1. 程式人生 > 其它 >P4363 [九省聯考2018]一雙木棋chess(輪廓線狀壓)

P4363 [九省聯考2018]一雙木棋chess(輪廓線狀壓)

P4363 [九省聯考2018]一雙木棋chess

這兩天學了這個。

是輪廓線狀壓的或許算是裸題。

關鍵在於怎麼壓狀態。

題意

  • 有一個 \(n \times m\) 的棋盤,兩個人輪流下棋。

  • 一個位置可以落子當且僅當這個位置的左側和上面都有棋子。

  • 兩個人落在對應的位置會收穫各自的貢獻值。

  • 最大化自己的得分減去對方的得分。

做法

博弈論?

是有 max_min搜尋還有一些別的做法的。

max_min搜尋

看眼資料範圍,都是 \(10\) 以內的。

於是考慮狀壓。

問題隨之而來:如何存狀態?

由於落子是有限制的,所以需要考慮在什麼情況下可以落子。

由於落子是合法的需要滿足左邊和上邊都有棋子,於是在整個過程中必然會有這樣的情況:

可以發現這樣的輪廓一定是連續的。

於是這就是我們需要的輪廓線。

可以考慮儲存右下角的輪廓線作為狀態。

如何儲存呢?

首先這個輪廓線的長度肯定是 \(n+m\) 的。

我們用 \(0\) 來表示橫邊,用 \(1\) 來表示豎邊。

可以舉個例子:

對於當前的狀態,從右上角開始,向左下角延伸,如果有橫邊那麼當前位就是 \(0\),有豎邊就是 \(1\)

所以狀態串就是 \(000101010101010101\)

之後對於狀態的判斷以及轉移過程,都可以利用這個輪廓線。

可以發現,經過這樣的狀壓之後,判斷是否可以落子的條件就變成了:是否存在一個位置,滿足串中有 \(01\) 這樣的兩位,如果有,那麼就可以落子。

落子以後,該位置的 \(01\)

就會變成 \(10\)

對於轉移過程中的具體操作,由於需要判斷狀態串中是否存在 \(01\),可以採用一種手法:

if(((now>>i)&3)!=1)continue;

由於 \(3\) 的二進位制是 \(011\) ,如果也就是判斷是否存在 \(01\)的情況。

之後改變狀態:

int nxt=now^(3<<i);

之後起始狀態和終止狀態就很明顯了:

起始狀態:\(000...00111...11\)

終止狀態:\(111...11000...00\)

之後就是關於如何 DP 的問題了。

其實這個事情是不好辦的。

首先這個題目要求我們兩個人都要走最優策略。

如果按照我們常規的 DP 思路的話,自然是要設 \(f[S]\)

為在 \(S\) 這個狀態下可以獲得多少得分差。

但是對於這種博弈論 DP 的題目是不可以這樣做的。

由於兩方都要採取最優策略,所以我們不得不考慮將來的行動對現在現在的影響。

如果正序 dp 的話也許會發生當前確實取到了最大值但是其實並不是最優策略。

於是我們可以考慮倒過來 dp。

可以假裝我們可以一步看到結局,然後選擇對自己最有利的狀態。

顯然棋盤被佈滿的狀態下沒有人可以獲得新的分數,那麼如果我們用 \(f[S]\) 表示從 \(S\) 輪廓線的狀態距離遊戲結束還能得多少分。

那麼我們會發現,如果這個局面是先手下完以後形成的,那麼如何這個局面如何轉移的主動權顯然是攥在後手的手裡,所以此時這個 dp 值應該由所有後繼狀態的相對於先手最劣的狀態轉移過來,也就是所有的後繼狀態取個 \(\min\)

如果是後手的話,dp值就應該是對於後手最劣的狀態轉移過來,也就是所有的後繼狀態取個 \(\max\)

也就是第一個人想要最大化兩者的差值,第二個人想要最小化這個差值。

//#define LawrenceSivan

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define re register
const int maxn=1e5+5;
#define INF 0x3f3f3f3f

int n,m;

int f[1<<21];

int a[15][15],b[15][15];

bool vis[1<<21];

int dfs(int now,int who){
	if(vis[now])return f[now];//記憶化搜尋
	vis[now]=true;
	f[now]= who?-INF:INF;//先手取max,所以初始最小,後手取min,所以初始最大
	
	int x=n,y=0;
	for(re int i=0;i<n+m-1;i++){
		if((now>>i)&1)x--;
		else y++;
		if(((now>>i)&3)!=1)continue;
		int nxt=now^(3<<i);
		if(who)f[now]=max(f[now],dfs(nxt,0)+a[x][y]);
		else f[now]=min(f[now],dfs(nxt,1)-b[x][y]);
	}
	
	return f[now];
}

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}

int main(){
#ifdef LawrenceSivan
    freopen("aa.in","r",stdin);
    freopen("aa.out","w",stdout);
#endif
	n=read();m=read();
	for(re int i=0;i<n;i++){
		for(re int j=0;j<m;j++){
			a[i][j]=read();
		}
	}
	for(re int i=0;i<n;i++){
		for(re int j=0;j<m;j++){
			b[i][j]=read();
		}
	}
	
	vis[((1<<n)-1)<<m]=true;//倒過來DP,即從結束狀態開始
	f[((1<<n)-1)<<m]=0;
	
	dfs((1<<n)-1,1);
	
	printf("%d\n",f[(1<<n)-1]);//最後找到開始狀態下對應的答案



	return 0;
}