E - Remove Pairs(记忆化搜索or状态压缩dp)

E - Remove Pairs(记忆化搜索or状态压缩dp)

该题是一个典型的状态压缩dp的题,但也可以用记忆化搜索来写。

该题题意:

​ A和B在玩游戏,桌子上有n个牌,每个牌的正面和背面都写上数字。每次可以进行如下操作:

  • 将两张正面和背面至少有一面相同的牌丢弃。

​ 根据以上规则,两个人轮流丢牌,直到丢不了为止,丢不了的那个人输。

​ A先手。

数据范围:

  • 1 < = N < = 18 1 <= N <= 18 1<=N<=18
  • 1 < = A i , B i < = 1 0 9 1 <= A_i,B_i <= 10^9 1<=Ai,Bi<=109
  • 所有输入的都是整数。

解题思路:

​ 首先要考虑到状态转移。这个题本身是个博弈的题,那么我们可以考虑到当下一个人没法选择两张牌的时候,那么就是当前状态赢。既然要考虑到状态转移,那么就需要进行状态转移的基本方法:

  • 起始状态: 起始状态下就是一个都没选,那就对应的每一个牌的状态都为1 (代码实现上可以用((1 << n) - 1) 来实现)。
  • 终止状态: 当下一步没法进行选取的时候,就是终止状态。
  • 状态转移方程: 我们可以看出来,每到下个一状态就是由上一个状态减去两张牌得来的,那么就可以得到nex = x ^ (1<<i) ^ (1 << j),这样得到的nex就是下一个状态需要的。

​ 整个思路可以这样理解,就是玩家会最优地选择策略,那么如果下个状态出现对手必败的情况,那么他就一定会到达那。

代码实现:

​ 代码实现上,可以用到记忆化搜索和状态压缩。

记忆化搜索:

const int N = 20;
const int M = 1 << 18;

pair<int,int> arr[N];
bool user[M];
int f[M],n;
int dfs(int x){
	if(user[x]) return f[x];  //记忆化
	for(int i = 0; i < n - 1; i++){
		if(!((1 << i) & x)) continue;  // 看是否已经被丢掉了
		for(int j = i + 1; j < n; j++){
			if(!((1 << j) & x)) continue;  // 和上面一样
			if(arr[i].first != arr[j].first && arr[i].second != arr[j].second) continue; // 判断是否能被丢掉
			int nex = x ^ (1 << i) ^ (1 << j); // 丢掉这两个张牌
			f[x] |= (!dfs(nex)); // 因为到下一个状态的时候,如果对方赢的话,那么我们就输,如果对方输的话,我们就赢
		}
	} 
	user[x] = true;  // 标记一下这个点是否被遍历过了。
	return f[x];
}
int main (void){
	cin >> n; 
	for(int i = 0; i < n; i++)  cin>>arr[i].first >> arr[i].second;
	
	dfs((1 << n) -1);
	cout << (f[((1 << n) - 1)] ? "Takahashi" : "Aoki");
	return 0;
} 

状态压缩dp:

const int N = 20;
const int M = 1 << 18;

pair<int,int> arr[N];
bool user[M];
int f[M], n;

int main (void){
	cin >> n; 
	for(int i = 0; i < n; i++)  cin>>arr[i].first >> arr[i].second;
	
	int S = (1 << n) - 1;
	for(int s = 0; s <= S; s++){  // 状态压缩 遍历从0 到 1 << n - 1,
		for(int i = 0; (1 << i) <= s; i++){
			if(!(s & (1 << i))) continue;  // 判断这个点是否已经被丢掉了
			for(int j = i + 1; (1 << j) <= s; j++){
				if(!(s & (1 << j))) continue; // 和上一条一样
				if(arr[i].first != arr[j].first && arr[i].second != arr[j].second) continue; // 判断是否能被丢掉
				int nex = s ^ (1 << i) ^ (1 << j); // 能丢就丢
				f[s] |= !f[nex];  // 判断丢掉后的这个点是赢还是输,如果对面赢,我们就输,如果对面输,我们就赢。
			}
		} 
	}
	cout << (f[S] ? "Takahashi" : "Aoki") << endl;
	return 0;
}

总结:

​ 这个题,当初看到的时候,以为是个博弈题,但看到这个数据范围后,也没想到什么解题思路,当时就直接放弃了,但现在看来,他好像是个博弈dp(看别人的,之前没听说过这个dp),但是仔细看来和博弈关系不大,就是一个典型的状态压缩dp,以后看到这种时间复杂度的题,就可以想到是个二进制状态压缩的题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值