作法:
這題是輪廓線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年5月13日 星期三
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 :
這題是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 :
這題用到了偏序集的 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 :
記 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 :
其實這題蠻像最大子矩陣的。如果把每個兩個格子的交界處都看成一個點,並且幫他們編坐標,並且讓不合法的交界(沒有滿足題目的那個小於的需求)的值是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 :
算比較容易看出來是 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 :
附上 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) ; } }
訂閱:
文章 (Atom)
