【2022省选模拟】Board Game——博弈论DP、压位

原创题又没有链接

题解

在这里插入图片描述

题解

这题可以先把棋盘的状态拍平成01序列,然后每个图形处理一下任意平移过后的状态,那么这些状态以及他们的超集都是必败态。

我们可以做一个朴素的 O ( n m 2 n m ) O(nm2^{nm}) O(nm2nm) 的沃尔什变换来预处理得到每个状态是否必定是必败态,然后再 O ( n m 2 n m ) O(nm2^{nm}) O(nm2nm) 做一下状压DP,用定义确定每个状态的胜负,这样可以得 50 分。

这个时候一定要想起神的教导:

如果DP状态里存的是bool的话,考虑用int存来优化状态。——JZM

我们发现不管是我们一开始的预处理还是后面的DP,每个位置存的都是 bool,非常浪费时间空间,所以我们考虑压位,把每64个bool分块,压缩到一个unsigned long long里面,这样数组只用开到 2 n m − 6 2^{nm-6} 2nm6 的大小(结合数据范围的提示: n × m ≥ 6 n\times m\ge 6 n×m6 😀)。

考虑第一步沃尔什变换,由于我们不用考虑算重,所以就用普通的从小到大枚举状态+枚举转移位的DP代替。我们先考虑跨块的转移,这个可以用按位或解决;块内转移可以枚举转移距离 2 i ( 0 ≤ i < 6 ) 2^i(0\le i<6) 2i(0i<6),对每个 i i i 预处理出哪些位置可以往后转移,然后就可以按位与+位移+按位或直接解决。这部分可以做到 O ( n m 2 n m − 6 ) O(nm2^{nm-6}) O(nm2nm6)

考虑第二步DP,我们还是先处理跨块的转移,相当于要先取反再按位或。块内转移可以直接枚举 64 个位置,( 此处要预处理每个位置可以从块内哪些位置转移过来),先取反,然后用按位与提取出来,判断是否含有1即可。由于操作较复杂,要先取反再或起来,所以这部分没法做到前面的优秀复杂度,只能做到 O ( 2 n m ) O(2^{nm}) O(2nm)

代码

卡常技巧:第二步DP的时候利用第一步预处理的信息剪枝,然后开 Ofast

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define lll __int128
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
#pragma GCC optimize("Ofast")
using namespace std;
const int MAXN=-1;
const ll INF=1e18;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

uns ll vis[1<<21],a[6],b[6],dp[1<<21],c[100];
bool as[30][30];
int n,m,k;
char in[30];
inline ll geths(int x,int y,int h,int w){
	ll res=0;
	for(int i=0;i<h;i++)
		for(int j=0;j<w;j++)
			if(as[i][j])res|=(1ll<<((x+i)*m+y+j));
	return res;
}
signed main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	n=read(),m=read(),k=n*m-6;
	for(int K=read();K--;){
		int h=read(),w=read();
		for(int i=0;i<h;i++){
			scanf("%s",in);
			for(int j=0;j<w;j++)as[i][j]=(in[j]=='1');
		}
		for(int i=0;i<=n-h;i++)
			for(int j=0;j<=m-w;j++){
				ll hs=geths(i,j,h,w);
				vis[hs>>6]|=(1ull<<(hs&63));
			}
	}
	for(int i=0;i<6;i++)
		for(int s=0;s<64;s++){
			if((s>>i)&1)b[i]|=(1ull<<s);
			else a[i]|=(1ull<<s);
		}
	for(int s=0;s<64;s++)
		for(int i=0;i<6;i++)if(~(s>>i)&1)c[s]|=(1ull<<(s|(1<<i)));
	for(int s=0;s<(1<<k);s++){
		for(int i=0;i<6;i++)
			vis[s]|=((vis[s]&a[i])<<(1<<i));
		for(int i=0;i<k;i++)if(~(s>>i)&1)
			vis[s|(1<<i)]|=vis[s];
	}
	for(int s=(1<<k)-1;s>=0;s--){
		dp[s]=0;
		for(int i=0;i<k;i++)
			if(~(s>>i)&1)dp[s]|=(~dp[s|(1<<i)]);
		dp[s]&=(~vis[s]);
		for(int i=63;i>=0;i--)if(~(vis[s]>>i)&1){
			uns ll f=(~dp[s])&c[i];
			if(f)dp[s]|=(1ull<<i);
			dp[s]&=(~vis[s]);
		}
	}
	printf((dp[0]&1)?"Alice\n":"Bob\n");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值