2018 省選 T1 一雙木棋
題目描述
菲菲和牛牛在一塊n 行m 列的棋盤上下棋,菲菲執黑棋先手,牛牛執白棋後手。 棋局開始時,棋盤上沒有任何棋子,兩人輪流在格子上落子,直到填滿棋盤時結束。
落子的規則是:一個格子可以落子當且僅當這個格子內沒有棋子且這個格子的左側及上方的所有格子內都有棋子。
棋盤的每個格子上,都寫有兩個非負整數,從上到下第i 行中從左到右第j 列的格 子上的兩個整數記作Ai,jA{i,j}Ai,j? 、Bi,jB{i,j}Bi,j? 。在遊戲結束後,菲菲和牛牛會分別計算自己的得分:菲菲的得分是所有有黑棋的格子上的Ai,jA{i,j}Ai,j? 之和,牛牛的得分是所有有白棋的格子上的Bi,jB{i,j}Bi,j? 的和。
菲菲和牛牛都希望,自己的得分減去對方的得分得到的結果最大。現在他們想知道,在給定的棋盤上,如果雙方都采用最優策略且知道對方會采用最優策略,那麽,最終的結果如何。 輸入輸出格式 輸入格式:
從文件chess.in 中讀入數據。
輸入第一行包含兩個正整數n;m,保證n;m <= 10。
接下來n 行,每行m 個非負整數,按從上到下從左到右的順序描述每個格子上的 第一個非負整數:其中第i 行中第j 個數表示Ai,jA_{i,j}Ai,j? 。
接下來n 行,每行m 個非負整數,按從上到下從左到右的順序描述每個格子上的 第二個非負整數:其中第i 行中第j 個數表示Bi,jB_{i,j}Bi,j? 。
輸出格式:
輸出到文件chess.out 中。
輸出一個整數,表示菲菲的得分減去牛牛的得分的結果。
分析:
首先,左邊、上邊所有格子和左邊格子、上邊格子都填滿其實是一樣的。
可以通過n/m <=10 想到狀壓dp
一般大家用的狀壓dp都是維護之前的幾行從左數有幾個旗子已經下過。
因為發現,棋盤上下過的地方總是右上角的一個階梯形狀,剩下的總是一個右下角的部分。所以高級的做法是:維護已下過的部分和沒有下過的部分的分界線的狀態。(1表示橫,0表示豎) 狀態查看時,從末位向前看,從棋盤左下角劃線。
例如樣例中,11100是初始狀態,00111是最終的狀態。
我們可以dfs預處理出所有的狀態,C(20,10)種合法狀態。接著,我麽可以預處理出每個狀態的轉彎處(0,1交匯處)通過這個轉彎處可以下一個棋子,從而轉移到下一個狀態。
需要註意的是最後work的方法。(又卡了一天)
不能用遞推!因為之前的局部最優策略下的最優解可能不是最終局面下的形式。而由於後面的局面“最優解”是通過這個局部策略轉移過來的,導致全部錯誤。
例如:
3 3
9 4 3
6 7 6
4 5 9
0 0 0
0 0 0
0 0 0
錯誤輸出是 29 正解 32
錯誤的過程是:
9 4 3
0 0 0
4 0 9
正解:
9 0 3
0 7 0
4 0 9
正解中,下棋時會先填滿左邊一列,而錯誤解法則是隨機的一塊中的當前最優解來更新。左上角三個數,錯誤解法中這就是局部最優解,但是與正解相差甚遠。
所以考慮設f[i]表示i狀態下,剩下的格子下法中最優解的答案(是一個a-b的差值)
當該a下時,初值f[i]=-inf f[i]=max(f[i],dfs(to,who^1)+a[x][y]); 當該b下時,初值f[i]=inf; f[i]=min(f[i],dfs(to,who^1)-b[x][y]);
加上記憶化搜索即可。(在這裏剪掉的是一種局面可能由多種局面下出來的情況,避免再往後推) 100行代碼,不開O2照樣水過。
總結: 1.應用的算法:狀壓dp與記憶化搜索結合。 2.註意轉移時的方式和順序。保證最優子結構。
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> using namespace std; const int N=12; const int M=184756+5; const int inf=0x3f3f3f3f; int f[M]; int ret[1048576]; struct node{ int wei[2*N][4]; int tur; int zhi; int size; }g[M]; int n,m; int a[N][N],b[N][N]; int cnt=0; int vis[M]; void dfs(int x,int sum,int num1,int num0) { if(x==(n+m)+1) { if(num1==m&&num0==n) { g[++cnt].zhi=sum; } return; } dfs(x+1,sum+(1<<x-1),num1+1,num0); dfs(x+1,sum,num1,num0+1); }//0 up 1 right int dfs2(int hao,int who) { if(vis[hao]) return f[hao]; int i=hao; vis[hao]=1; if(hao==cnt) return 0; if(who&1) f[hao]=inf; else f[hao]=-inf; for(int i=1;i<=g[hao].tur;i++) { int h=g[hao].wei[i][0]; int l=g[hao].wei[i][1]; int x=g[hao].wei[i][2]; int y=g[hao].wei[i][3]; int to=ret[g[hao].zhi+(1<<x-1)-(1<<y-1)]; if(who&1) f[hao]=min(f[hao],dfs2(to,1-who)-b[h][l]); else f[hao]=max(f[hao],dfs2(to,1-who)+a[h][l]); } return f[hao]; } bool cmp(node a,node b) { if(a.size!=b.size) return a.size<b.size; return a.zhi>b.zhi; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&b[i][j]); dfs(1,0,0,0); for(int i=1;i<=cnt;i++) { int t=g[i].zhi; int s=0; int last; int num[2]; memset(num,0,sizeof num); while(s!=(n+m)) { s++; if(t&1) g[i].size+=n-num[0]; if(s==1) last=(t&1); else{ if(last==0&&((t&1)==1)) { g[i].wei[++g[i].tur][0]=n-num[0]+1; g[i].wei[g[i].tur][1]=num[1]+1; g[i].wei[g[i].tur][2]=s-1; g[i].wei[g[i].tur][3]=s; } last=(t&1); } num[t&1]++; t>>=1; } } sort(g+1,g+cnt+1,cmp); for(int i=1;i<=cnt;i++) ret[g[i].zhi]=i; printf("%d",dfs2(1,0)); return 0;
}
2018 省選 T1 一雙木棋