顯示具有 ZJ 標籤的文章。 顯示所有文章
顯示具有 ZJ 標籤的文章。 顯示所有文章

2015年5月13日 星期三

[ZJ a228] 就少一個插座用很不方便

作法:

這題是輪廓線DP的最簡單的題型(插頭DP好像是專指要把輪廓線上的連通性壓成狀態的DP,不過名詞似乎沒那麼重要),這裡大概提一下輪廓線DP一般的想法。首先我們知道,「多段圖的決策問題」是最好DP的東西,具體來說就是,想像現在有好幾陀節點,第 $i$ 陀節點只有連往第 $i+1$ 陀節點的邊,問起點走到終點總共有多少條路徑。這顯然可以一陀一陀算,第 $i$ 陀所有節點的答案只和第 $i-1$ 陀所有節點的答案有關,並且轉移式是顯然的。也就是當我們現在要DP的圖是多段圖時,只要確定好每陀中有哪些節點,還有知道這些節點會連向哪些節點(也就是這個狀態可以轉移到哪些狀態,或稱為這個節點的所有決策),那麼就可以簡單的DP了。至於原本的問題我們也可以把他看成一個多段圖決策問題,假設現在已經有一條路徑滿足題目要求了,那麼考慮把所有格子拆開,每個格子只會是以下六種可能之一:

或是空白的(圖中的障礙),因此我們可以把這個問題看成「拼拼圖」的問題,考慮從上而下,從左而右一塊一塊把拼圖放上去(如果這格是障礙格那就只能放沒有線的拼圖),因此我們可以用「當前要放拼圖的格子」來劃分多段圖的每個階段,並且每個節點的決策就是放上一塊拼圖。至於每一陀中到底有哪些節點,總不能把當前格之前的所有格子的每一種放法都開一個節點。而觀察下圖可以發現,如果當前格是綠色叉叉所在的格子,那麼會對之後狀態有影響的只有「紅色的邊上是否有線通過」,不管紅線以上的路徑長成什麼歪來歪去的樣子。

因此我們可以用一個長$m+1$的$01$字串代表一個狀態。而在轉移時還要注意一些事情,以上圖為例,當前格就不能放下第 $1,3,4,5$ 種拼圖,因為第 $1,4,5$ 種拼圖的左邊有線,但這個狀態相應的位子沒有線,所以不能放。而第 $3,4,5$ 種拼圖的上面沒有線,但當前狀態相應的位子有線,所以也不能放。最後也要注意到:如果當前格在盤面的右邊界,那麼就不可以放右邊有線的拼圖,同理在下邊界時也不能放下面有線的拼圖。

code :

2015年3月8日 星期日

[TIOJ 1473][ZJ b179] 空罐 Cans

作法:

這題是AC自動機上的DP,第一次寫這種東西,還蠻有趣的XD

因為一個字串相對於「一堆病毒字串」的關係其實只要用「這個字串在這些病毒字串建的AC自動機上匹配到哪個節點」就可以完整的表達它了,並且要記得在建AC自動機的時候,把哪些節點是不合法的( 會含有病毒字串的 )先標記好。這樣就可以在AC自動機上DP了,設 dp[ i ][ j ][ k ] 代表過了 i 天,長度為 j 且會匹配到自動機上節點 k 的字串數量,第一種轉移是在字串後加上 a , b , c , d ,他得到的新字串就是直接沿AC自動機轉移的結果。另外一種則是他自己長度要減少1,如果他目前的長度就是 1 了,那就把他丟進回收場。如果不是,我們想要知道所有在AC自動機上特定節點的字串,把它砍掉第一個字母後會轉移到哪。而一個字串 S 被匹配到AC自動機上的節點 k 的時候,其實是代表「考慮從自動機起點走到所有節點的路徑所產生的字串,則 k 形成的字串會是 S 的後綴,且他是所有節點裡面長度最長的」。所以如果在自動機上的節點多記錄一個 len ,存這個節點代表的字串長度,那麼可以知道,當 j = len[ k ] 的時候,也就是這個字串在匹配的過程沒有經過 fail 的邊,所以如果把第一個字母砍掉,他就會落到 fail[ k ] ,而如果 j > len[ k ] 的話,砍掉第一個字母不會對他的狀態有影響,因為我們需要的是最長的在自動機上的後綴,所以他會直接轉移到 k 。另外在每天的轉移結束後要把不合法的狀態(會含有病毒字串的狀態)砍掉,丟進醫院。

code :

#include<bits/stdc++.h>
#define MOD 10007
using namespace std;
const int maxn=100+10 ;
 
int ch[maxn*15][4] , len[maxn*15] , ccnt=1 ;
bool val[maxn*15] ;
void insert(char *t)
{
    int l=strlen(t) , now=0 ;
    for(int i=0;i<l;i++)
    {
        int c=t[i]-'a' ;
        if(!ch[now][c])
        {
            memset(ch[ccnt],0,sizeof(ch[ccnt])) ;
            ch[now][c]=ccnt ;
            len[ccnt]=len[now]+1 ;
            ccnt++ ;
        }
        now=ch[now][c] ;
    }
    val[now]=1 ;
}
 
int fail[maxn*15] ;
void get_fail()
{
    queue<int> q ;
    for(int i=0;i<4;i++) if(ch[0][i])
        fail[ch[0][i]]=0 , q.push(ch[0][i]) ;
    while(!q.empty())
    {
        int u=q.front() ; q.pop() ;
        for(int i=0;i<4;i++)
        {
            int v=ch[u][i] ;
            if(!v) { ch[u][i]=ch[fail[u]][i] ; continue ; }
            q.push(v) ;
            fail[v]=ch[fail[u]][i] ;
            if(val[fail[v]]) val[v]=1 ;
        }
    }
}
 
inline void up(int &x,int y)
{
    x=(x+y)%MOD ;
}
 
int n,dp[2][maxn][maxn*15] ;
char s[maxn] ;
 
void DP()
{
    int st=0 , L=strlen(s) ;
    for(int i=0;i<L;i++)
    {
        st=ch[st][s[i]-'a'] ;
        if(val[st]) { printf("0 1\n") ; return ; }
    }
    dp[1][L][st]=1 ;
    int ans1=0 , ans2=0 ;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<maxn;j++) for(int k=0;k<ccnt;k++)
            dp[(i+1)%2][j][k]=0 ;
        for(int j=1;j<maxn;j++) for(int k=0;k<ccnt;k++)
            if(dp[i%2][j][k])
        {
            int add=dp[i%2][j][k] ;
            for(int z=0;z<4;z++)
                up(dp[(i+1)%2][j+1][ch[k][z]],add) ;
            if(j==1) { up(ans1,add) ; continue ; }
            if(j==len[k]) up(dp[(i+1)%2][j-1][fail[k]],add) ;
            else up(dp[(i+1)%2][j-1][k],add) ;
        }
        for(int j=1;j<maxn;j++) for(int k=0;k<ccnt;k++)
            if(val[k])
            up(ans2,dp[(i+1)%2][j][k]) ,
            dp[(i+1)%2][j][k]=0 ;
    }
    printf("%d %d\n",ans1,ans2) ;
}
 
char t[maxn] ;
main()
{
    int k ;
    scanf("%s%d%d",s,&n,&k) ;
    while(k--) scanf("%s",t) , insert(t) ;
    get_fail() ;
    DP() ;
}
 

[TIOJ 1472][ZJ b178] 遊輪 Boat

作法:

這題用到了偏序集的 Dilworth 定理,之前在選訓不知道為什麼根余紅勳、趙庭偉研究了好久那些東西,沒想到資訊竟然會出現這個XD

如果考慮建一張圖,頂點是那些船,並且如果兩艘船可以停在同一個碼頭就在他們之間連邊,那麼問題就變成我們要用最少的完全圖來覆蓋所有的頂點。而到這裡其實可以猜說答案就是在圖裡「兩兩沒有連邊的頂點子集合的最大個數」。所以只要對每條線段,看有哪些線段的左端點在他的左端點右邊,且和他不能在同一個碼頭,然後把這些線段 sort ( x 從小到大,一樣時 y 從大到小 ) ,求 y 坐標的 LIS 長度就好了。

至於這件事為什麼是對的,考慮一個偏序集( 嚴謹定義可參考維基 ),他的元素是這些線段們,並且如果兩條線段有包含或是不相交的情形,就在他們之間定義小於,定法是先比左端點大小再比右端點大小,而如果兩條線段不是上面的情形(也就代表他們不能用同一個碼頭),那就讓他們沒辦法比大小。並且可以知道這樣定會滿足若 a>b , b>c ,則 a>c ,所以這樣定是沒問題的。這樣題目就變成要用最少條鍊( chain ) ( 滿足其內部的元素兩兩可比大小 )去覆蓋這個偏序集的所有元素,而 Dilworth 定理說他的數量恰等於最大反鍊( antichain )( 滿足其內部的元素兩兩不可比大小 )的大小,就得證了。

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000+10 ;
 
struct P
{
    int x,y ;
    bool operator < (const P &rhs) const
    {
        return x==rhs.x ? y>rhs.y : x<rhs.x ;
    }
}a[maxn],b[maxn];
 
vector<int> v ;
int dp[maxn] ;
int LIS()
{
    int num=0 ;
    dp[0]=0 ;
    for(auto i : v)
    {
        int id=lower_bound(dp,dp+num+1,i)-dp ;
        dp[id]=i ;
        if(id==num+1) num++ ;
    }
    return num ;
}
 
int solve(int num)
{
    sort(b,b+num) ;
    v.clear() ;
    for(int i=0;i<num;i++) v.push_back(b[i].y) ;
    return LIS() ;
}
 
main()
{
    int n ; scanf("%d",&n) ;
    for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y) ;
    int ans=0 ;
    for(int i=1;i<=n;i++)
    {
        int cnt=0 ;
        b[cnt++]=a[i] ;
        for(int j=1;j<=n;j++) if(i!=j)
        {
            if(a[i].x>=a[j].x) continue ;
            if(a[j].y<=a[i].y) continue ;
            if(a[j].x>a[i].y) continue ;
            b[cnt++]=a[j] ;
        }
        ans=max(ans,solve(cnt)) ;
    }
    printf("%d\n",ans) ;
}
 

[TIOJ 1471][ZJ b177] 山景 Skyline

作法:

記 dp[ i ][ j ] 是「走了 i 步且目前高度為 j ,且從此之後一直往下走到底是一條合法路徑」的路徑總數,就可以遞推得出了。但每次要算 dp[ i ][ j ] 的時候,會發現他其實會是一塊三角形(或是被砍一角的三角形)裡面的點的 dp 值總和,總不能一個一個加,所以會需要多維護兩個 sum 的陣列,一個代表 dp[ i ][ j ] +dp[ i-1 ][ j-1] +...  的值,一個代表 dp[ i ][ j ] + dp[ i+1 ][ j-1 ] +... 的值,就可以維護那塊三角形裡的數的總和,O(1)轉移。但這樣會爆記憶體,我只好丟建表的 code QQ ( 直接開一個 ans[ 1501 ] = { 答案們 }  )。

還有一點很陰險,就是他要的是輸出後 9 位,所以 >= 10^8 的數都會需要補0,小於的就不用,我傳上 ZJ 才知道還有這招QQ

code :

#include<bits/stdc++.h>
#define MOD 1000000000
const int maxn=3000+10 ;
 
int sum1[maxn][maxn],sum2[maxn][maxn] ;
 
main()
{
    int n,s ; scanf("%d",&n) ;
    int ans=0 ;
    for(int i=1;i<=n;i++) for(int j=0;j<=n;j++)
    {
        if(j>i)
        {
            s=(s+sum1[i][j-1])%MOD ;
            s-= (j>=2*i+1) ? sum2[i][j-2*i-1] : sum2[j-i-1][0] ;
            s=(s%MOD+MOD)%MOD ;
        }
 
        if(i>j || ((i+j)%2))
        {
            sum1[i][j]= (j ? sum1[i-1][j-1] : 0) ;
            sum2[i][j]=sum2[i-1][j+1] ;
            continue ;
        }
 
        if(i==j)
        {
            s=0 ;
            sum1[i][j]=(sum1[i-1][j-1]+1)%MOD ;
            sum2[i][j]=(sum2[i-1][j+1]+1)%MOD ;
            if(i+j==n) ans++ ;
            continue ;
        }
 
        if(i+j==n) ans=(ans+s)%MOD ;
        sum1[i][j]=(sum1[i-1][j-1]+s)%MOD ;
        sum2[i][j]=(sum2[i-1][j+1]+s)%MOD ;
    }
    if(n<=50) printf("%d\n",ans) ;
    else printf("%09d\n",ans) ;
}
 

[TIOJ 1470][ZJ b176] 區域 Area

作法:

其實這題蠻像最大子矩陣的。如果把每個兩個格子的交界處都看成一個點,並且幫他們編坐標,並且讓不合法的交界(沒有滿足題目的那個小於的需求)的值是0,合法的交界的值是1,就可以變回要求的就是這個東西的最大全部都是1的子矩陣。但還有些細節要注意,因為建出來的新的方格表大概會長這樣:

 0 1 1 0 1 1 1
1 0 0 1 1 1 1 1
 1 1 1 1 1 1 0
1 0 0 1 0 1 1 1

( 其中左上角坐標是(1,1) )
那麼我們要的其實是「四個角落的座標都是偶數」的子矩陣,並且剩下的格子不造成影響,所以可以當作是全部都填1,這樣就可以直接套用最大子矩陣的算法,只要不在不為所求的格點上更新答案就可以了。這樣複雜度一樣是O(n^3)。

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=200+10 ;
 
int G[maxn][maxn] ;
 
bool check(int x,int y)
{
    assert((x+y)%2) ;
    if(x==1 || y==1) return 0 ;
    if(x%2) return G[(x-1)/2][y/2]<G[(x+1)/2][y/2] ;
    else return G[x/2][(y-1)/2]<G[x/2][(y+1)/2] ;
}
 
struct P{int x,h;};
 
int h[maxn] ;
P st[maxn] ;
 
main()
{
    int n,m ; scanf("%d%d",&n,&m) ;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
        scanf("%d",&G[i][j]) ;
    int ans=0 ;
    for(int i=2;i<=2*n;i++)
    {
        for(int j=1;j<=2*m;j++)
            h[j]=  (((i+j)%2) && !check(i,j)) ? 0 : h[j]+1 ;
        if(i%2) continue ;
 
        int sz=0 ;
        for(int j=1;j<=2*m;j++)
        {
            while(sz>=2 && st[sz-2].h>=h[j]) sz-- ;
            if(sz && st[sz-1].h > h[j]) st[sz-1].h = h[j] ;
            if(j%2) continue ;
            st[sz++]=(P){j,h[j]} ;
            for(int k=0;k<sz;k++) ans=max(ans,
                ((j-st[k].x+2)/2)*((st[k].h+1)/2)) ;
        }
    }
    printf("%d\n",ans) ;
}
 

[TIOJ 1469][ZJ b183] 制服發放問題 / 4. 制服發放

作法:

算比較容易看出來是 flow 的題目,高市賽竟然考過 flow @@
首先可以把題目給的那些人相同的合併起來,就會變成有 a0 個 XXL + XL 、 a1 個 XXL + L
等等,所以題目要的就是把 a0 個 XXL + XL 分配給 XXL 和 XL 這兩個衣服,使得衣服的數量夠用。所以考慮一張圖,從6個代表衣服尺寸的節點往匯點建容量為 n/6 的邊,另外讓兩件衣服的那些限制條件也代表一個點,從源點往這些點建容量為限制個數的邊,例如源點往代表「 XXL + XL」的點建一條容量為 a0 的邊,然後求最大流,如果最大流 = m 就有解,反之就無解。

code :

#include<bits/stdc++.h>
#define INF 1000000000
using namespace std;
const int maxn=200 ;
struct P{int from,to,flow,cap;};
vector<P> edge ;
vector<int> v[maxn] ;
 
struct Dinic
{
    int st,ed ;
 
    void add_edge(int from,int to,int cap)
    {
        edge.push_back((P){from,to,0,cap}) ;
        edge.push_back((P){to,from,0,0}) ;
        int m=edge.size() ;
        v[from].push_back(m-2) ;
        v[to].push_back(m-1) ;
    }
 
    bool vis[maxn] ;
    int d[maxn] ;
    bool BFS()
    {
        memset(vis,0,sizeof(vis)) ;
        queue<int> q ;
        d[st]=0 ; q.push(st) ; vis[st]=1 ;
        while(!q.empty())
        {
            int u=q.front() ; q.pop() ;
            for(auto i:v[u])
            {
                P &e=edge[i] ;
                if(!vis[e.to] && e.cap>e.flow)
                {
                    vis[e.to]=1 ;
                    d[e.to]=d[u]+1 ;
                    q.push(e.to) ;
                }
            }
        }
        return vis[ed] ;
    }
 
    int cur[maxn] ;
    int DFS(int x,int a)
    {
        if(x==ed || a==0) return a ;
        int Flow=0 , f ;
        for(int &i=cur[x];i<v[x].size();i++)
        {
            P &e=edge[v[x][i]] ;
            if(d[e.to]==d[x]+1 && (f=DFS(e.to,min(a,e.cap-e.flow)))>0)
            {
                Flow+=f ;
                e.flow+=f ;
                edge[v[x][i]^1].flow-=f ;
                a-=f ;
                if(a==0) break ;
            }
        }
        return Flow ;
    }
 
    int MaxFlow(int st,int ed)
    {
        this->st = st ;
        this->ed = ed ;
        int Flow=0 ;
        while(BFS())
        {
            memset(cur,0,sizeof(cur)) ;
            Flow+=DFS(st,INF) ;
        }
        return Flow ;
    }
}di;
 
string st[]={"XXL","XL","L","M","S","XS"} ;
int num[16] ;
 
main()
{
    int n,m ;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        edge.clear() ;
        for(int i=0;i<maxn;i++) v[i].clear() ;
        memset(num,0,sizeof(num)) ;
        for(int z=0;z<m;z++)
        {
            string s,t ;
            cin >> s >> t ;
            int cnt=0 ;
            for(int i=0;i<6;i++) for(int j=i+1;j<6;j++,cnt++)
                if((s==st[i]&&t==st[j])||(t==st[i]&&s==st[j]))
                    num[cnt]++ ;
        }
        int cnt=0 ;
        for(int i=0;i<6;i++) di.add_edge(i,21,n/6) ;
        for(int i=0;i<6;i++) for(int j=i+1;j<6;j++,cnt++)
            di.add_edge(cnt+6,i,INF) ,
            di.add_edge(cnt+6,j,INF) ,
            di.add_edge(22,cnt+6,num[cnt]) ;
        if(di.MaxFlow(22,21)==m) printf("YES\n") ;
        else printf("NO\n") ;
    }
}
 

2015年2月24日 星期二

15-puzzle(二) 之 莫名其妙的常數(?)

接續15-puzzle(一),後來又查到了這篇文章,他的IDA* 寫法跟我的不太一樣,而且又可以估計下一次的 maxdep 應該要取多少比較好,所以我就照著他的方法寫,但是覺得那個 114/100 真是太詭異了先不理他,果然執行時間會比上一個 code 還要短,但傳到 ZJ 還是 TLE 了,後來我才領悟那個 114/100 的精髓(?),不過感覺還蠻唬爛的XD 剪掉了一些不太可能是檞但又有可能是解的狀態XDD 後來我試著把常數亂改( 加減幾個 1/100 之類的 ) ,發現大部分的情形都還是會TLE,或是如果常數在大一點會WA ( 因為把正解的狀態剪掉了 ), 114/100 真是一個剛好的值.....而且加了這個之後竟然比原本不乘這個常數跑的速度快了5~10倍左右......真是太強大啦!!!! XD

附上 AC了 ZJ d207 的 code ,但很詭異的是傳到UVa 竟然會TLE......怎麼想都沒道理阿OAO,不過AC就是AC啦XD(哪招

code :

#include<bits/stdc++.h>
using namespace std;
 
int a0[35][4]={ {0,0,0,4},{0,0,1,3},{0,0,2,2},{0,0,3,1},{0,0,4,0},
{0,1,0,3},{0,1,1,2},{0,1,2,1},{0,1,3,0},{0,2,0,2},{0,2,1,1},{0,2,2,0},
{0,3,0,1},{0,3,1,0},{0,4,0,0},{1,0,0,3},{1,0,1,2},{1,0,2,1},{1,0,3,0},
{1,1,0,2},{1,1,1,1},{1,1,2,0},{1,2,0,1},{1,2,1,0},{1,3,0,0},{2,0,0,2},
{2,0,1,1},{2,0,2,0},{2,1,0,1},{2,1,1,0},{2,2,0,0},{3,0,0,1},{3,0,1,0},
{3,1,0,0},{4,0,0,0} } ;
 
int b0[20][4]={ {0,0,0,3},{0,0,1,2},{0,0,2,1},{0,0,3,0},{0,1,0,2},
{0,1,1,1},{0,1,2,0},{0,2,0,1},{0,2,1,0},{0,3,0,0},{1,0,0,2},{1,0,1,1},
{1,0,2,0},{1,1,0,1},{1,1,1,0},{1,2,0,0},{2,0,0,1},{2,0,1,0},{2,1,0,0},
{3,0,0,0} } ;
 
struct P
{
    int x,y,z ;
    bool operator < (const P &rhs) const
    {
        if(x!=rhs.x) return x<rhs.x ;
        if(y!=rhs.y) return y<rhs.y ;
        return z<rhs.z ;
    }
};
 
struct state
{
    int a[16] ;
    int zero ;
    bool scan()
    {
        if(scanf("%d",&a[0])==EOF) return 0 ;
        zero=0 ;
        for(int i=1;i<16;i++)
        {
            scanf("%d",&a[i]) ;
            if(!a[i]) zero=i ;
        }
        return 1 ;
    }
};
 
int __d[1500000] ;
struct WalkingDistance
{
    int cod[5] ;
    map<P,int> mpa,mpb ;
    int encode()
    {
        int ret=0 ;
        for(int i=0;i<4;i++) ret=ret*35+cod[i] ;
        return ret ;
    }
 
    void decode(int x)
    {
        for(int i=0;i<4;i++) cod[3-i]=x%35 , x/=35 ;
    }
 
    int num[4][4] ;
    void cal()
    {
        for(int i=0;i<35;i++) mpa[(P){a0[i][0],a0[i][1],a0[i][2]}]=i ;
        for(int i=0;i<20;i++) mpb[(P){b0[i][0],b0[i][1],b0[i][2]}]=i ;
 
        queue<int> q ;
        memset(__d,-1,sizeof(__d)) ;
        int *d = __d ;
        cod[0]=mpa[(P){4,0,0}] , cod[1]=mpa[(P){0,4,0}] ,
        cod[2]=mpa[(P){0,0,4}] , cod[3]=mpb[(P){0,0,0}] ;
        int start=encode() ; d[start]=0 ; q.push(start) ;
        while(!q.empty())
        {
            int u=q.front() ; q.pop() ;
            decode(u) ;
            int num2[4]={0} ;
            for(int i=0;i<4;i++) for(int j=0;j<4;j++)
                num[i][j]= i==3 ? b0[cod[i]][j] : a0[cod[i]][j] ,
                num2[j]+=num[i][j] ;
            int emp ;
            for(emp=0;emp<4 && num2[emp]==4;emp++) ;
 
            for(int i=-1;i<=1;i+=2) if(emp+i>=0 && emp+i<4)
                for(int j=0;j<4;j++) if(num[j][emp+i])
            {
                num[j][emp+i]-- ;
                num[j][emp]++ ;
                for(int z=0;z<4;z++) cod[z]= z==3 ?
                    mpb[(P){num[z][0],num[z][1],num[z][2]}] :
                    mpa[(P){num[z][0],num[z][1],num[z][2]}] ;
                int newu=encode() ;
                if(d[newu]==-1) d[newu]=d[u]+1 , q.push(newu) ;
                num[j][emp+i]++ ;
                num[j][emp]-- ;
            }
        }
    }
 
    int get_wd(const state &s)
    {
        int ret=0 ;
        memset(num,0,sizeof(num)) ;
        for(int i=0;i<16;i++) if(s.a[i])
            num[(s.a[i]-1)/4][i/4]++ ;
        for(int z=0;z<4;z++) cod[z]= z==3 ?
            mpb[(P){num[z][0],num[z][1],num[z][2]}] :
            mpa[(P){num[z][0],num[z][1],num[z][2]}] ;
        ret+=__d[encode()] ;
 
        memset(num,0,sizeof(num)) ;
        for(int i=0;i<16;i++) if(s.a[i])
            num[(s.a[i]-1)%4][i%4]++ ;
        for(int z=0;z<4;z++) cod[z]= z==3 ?
            mpb[(P){num[z][0],num[z][1],num[z][2]}] :
            mpa[(P){num[z][0],num[z][1],num[z][2]}] ;
        ret+=__d[encode()] ;
 
        return ret ;
    }
 
    void set_num(const state &s,int type)
    {
        memset(num,0,sizeof(num)) ;
        for(int i=0;i<16;i++) if(s.a[i])
        {
            if(type==1) num[(s.a[i]-1)/4][i/4]++ ;
            else num[(s.a[i]-1)%4][i%4]++ ;
        }
    }
 
    int get_wd()
    {
        for(int z=0;z<4;z++) cod[z]= z==3 ?
            mpb[(P){num[z][0],num[z][1],num[z][2]}] :
            mpa[(P){num[z][0],num[z][1],num[z][2]}] ;
        return __d[encode()] ;
    }
}wd1,wd2;
 
bool check(const state &s)
{
    int x=0,pos=0 ;
    for(int i=0;i<16;i++)
    {
        if(s.a[i]) for(int j=i+1;j<16;j++)
        {
            if(s.a[j] && s.a[j]<s.a[i])
                x++ ;
        }
        else pos=i ;
    }
    x+=(pos/4) ;
    return x%2 ;
}
 
state st ;
int dx[]={1,0,0,-1} , dy[]={0,1,-1,0} ;
int solved,maxdep ;
char ch[]={'D','R','L','U'} , path[100] ;
int iddfs(int dep,int val1,int val2,int pre)
{
    if(!(val1+val2))
    {
        solved = dep ;
        return dep ;
    }
    if(dep + (val1+val2)*114/100 > maxdep)
        return dep+(val1+val2)*114/100 ;
 
    int z=st.zero , x=z/4 , y=z%4 ;
    int submaxd = 0xfff ;
    for(int i=0;i<4;i++) if(i+pre!=3)
    {
        int nx=x+dx[i] , ny=y+dy[i] ;
        if(nx<0||nx>3||ny<0||ny>3) continue ;
        int nz=4*nx+ny ;
 
        int newval1=val1,newval2=val2 ;
        if(x!=nx)
            wd1.num[(st.a[nz]-1)/4][nx]-- , wd1.num[(st.a[nz]-1)/4][x]++ ,
            newval1 = wd1.get_wd() ;
        else
            wd2.num[(st.a[nz]-1)%4][ny]-- , wd2.num[(st.a[nz]-1)%4][y]++ ,
            newval2 = wd2.get_wd() ;
        swap(st.a[z],st.a[nz]) ; st.zero=nz ;
 
        path[dep]=ch[i] ;
        int tmp=iddfs(dep+1,newval1,newval2,i) ;
 
        swap(st.a[z],st.a[nz]) ; st.zero=z ;
        if(x!=nx)
            wd1.num[(st.a[nz]-1)/4][nx]++ , wd1.num[(st.a[nz]-1)/4][x]-- ;
        else
            wd2.num[(st.a[nz]-1)%4][ny]++ , wd2.num[(st.a[nz]-1)%4][y]-- ;
 
        if(solved) return solved ;
        submaxd=min(submaxd,tmp) ;
    }
    return submaxd ;
}
 
main()
{
    wd1.cal() ; wd2.cal() ;
    int T ; scanf("%d",&T) ;
    while(T--)
    {
        st.scan() ;
        int start=clock() ;
        if(!check(st))
            { printf("This puzzle is not solvable.\n") ; continue ; }
 
        wd1.set_num(st,1) ;
        wd2.set_num(st,2) ;
        solved=0 ;
        int val1,val2 ;
        val1=wd1.get_wd() ;
        val2=wd2.get_wd() ;
        maxdep=val1+val2 ;
        while(!solved)
        {
//            printf("now depth = %d\n",maxdep) ;
            maxdep = iddfs(0,val1,val2,-1) ;
        }
        printf("%d",solved) ;
//        for(int i=0;i<maxdep;i++) printf("%c",path[i]) ;
        printf("\n") ;
//        int end=clock() ;
//        printf("total use %.4f s \n",((double)(end-start))/CLOCKS_PER_SEC) ;
    }
}