1. 程式人生 > 其它 >#輪廓線dp,博弈論#洛谷 4363 [九省聯考 2018] 一雙木棋 chess

#輪廓線dp,博弈論#洛谷 4363 [九省聯考 2018] 一雙木棋 chess

題目傳送門


分析

菲菲想讓答案儘量大,牛牛想讓答案儘量小。

很天真的一種想法就是設 \(dp[i][j]\) 表示現在選擇 \((i,j)\) 的答案。

但是這樣有一個弊端就是並不知道其它位置怎麼選擇。

準確來說,已經被選擇的位置和未被選擇的位置存在一條分割線,或者直接叫輪廓線。

設橫著的輪廓表示0,豎著的輪廓表示1,那麼一開始的輪廓線從左下往右上看就是 \(n\)\(1\)\(m\)\(0\)

然後每次選擇的位置就是形如 \(\dots10\dots\)\(0\) 所在的位置,每次選擇一個合適的位置將這個數算進貢獻中。

可以發現每種輪廓線只能被其中一個人選擇,轉移大概就是如果是菲菲操作,那麼加 \(a_{x,y}\)

取最大值,否則牛牛減 \(b_{x,y}\) 取最小值。

那麼這樣可用的狀態實則為 \(\binom{n+m}{n}\) 個,記憶化一下就可以了。


程式碼

#include <iostream>
#include <cstring>
using namespace std;
const int _inf=0xcfcfcfcf;
int dp[1<<20],n,m,a[11][11],b[11][11];
void Min(int &x,int y){x=x<y?x:y;}
void Max(int &x,int y){x=x>y?x:y;}
int dfs(int S,int opt){
	if (dp[S]!=_inf) return dp[S];
	if (opt) dp[S]=-dp[S];
	int x=1,y=m;
	for (int i=0;i<(n+m-1);++i){
		if (((S>>i)&3)==2){
			int _S=S^(3<<i);
			if (!opt) Max(dp[S],dfs(_S,1)+a[x][y]);
			    else Min(dp[S],dfs(_S,0)-b[x][y]);
		}
		if ((S>>i)&1) ++x;
		    else --y;
	}
	return dp[S];
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for (int i=1;i<=n;++i)
	for (int j=1;j<=m;++j) cin>>a[i][j];
	for (int i=1;i<=n;++i)
	for (int j=1;j<=m;++j) cin>>b[i][j];
	memset(dp,0xcf,sizeof(dp));
	dp[(1<<n)-1]=0;
	cout<<dfs(((1<<n)-1)<<m,0);
	return 0;
}