COMPFEST 15 - Preliminary Online Mirror I. Imagination Castle(dp递推+sg函数思想)

题目

n*m(1<=n,m<=2e5)的棋盘,有k(k<=min(n*m,2e5))个特殊位置,

初始时在(1,1)位置,Chaneka和Bhinneka两人玩游戏,Chaneka先手,

当你在(x,y)位置时,下一步可以走到同一行靠右的位置(x'=x,y'>y)或同一列靠下的位置(x'>x,y'=y)

保证(1,1)不是特殊位置,若走到特殊位置则获胜,若无法操作则失败

问双方都最优操作下,谁获胜

思路来源

https://www.cnblogs.com/cjjsb/p/17693794.html#i-imagination-castle

题解

首先会想到n*m的sg打表,sg值转移可以大于1,但是本题是没有必要的,

只需关注sg=0和sg=1两个值就可以了,也就是先手必胜和先手必败态

对于特殊位置,由于先手站在其同行左侧或者同列上侧,走一步就赢了

后手同理,所以两人不会往这些地方上走,

对于每个特殊位置,可以给其同行左侧和同列上侧这些地方打X

对剩下的图中O的部分跑sg,再判断(1,1)是不是一个先手必败点,

画圈的地方是先手必败点,复杂度O(n*m)

考虑我们一开始最右下角,倒着推sg值的过程,

1. (x,y)在右下角,必败,sg(x,y)=0,同时第x行左侧和第y列上侧所有sg值为1,接着考虑(x-1,y-1)

2. (x,y)被X覆盖,要么当前位置有棋子,要么右侧有棋子,要么下方有棋子

①如果当前位置有棋子,说明当前位置是一个必败态,接着考虑(x-1,y-1),和第一种情况类似

②如果仅右侧有棋子,说明当前位置必胜态,同行左侧都是必胜态,同行不可能再出现必败态了,但同列下方都是必胜态,还有可能在上方出现必败态,接着考虑(x-1,y)

③如果仅下放有棋子,说明当前位置必胜态,同列上侧都是必胜态,同列不可能再出现必败态了,

但同行右侧都是必胜态,还可能在左侧出现必败态,接着考虑(x,y-1)

然后发现,这么移动指针时,中途经过的点,只可能有这两种情况,

要么(x,y)是一个必败态,要么(x,y)被X覆盖,简单证明没有被X覆盖的一定是必败态

1. 因为当(x,y)没有被X覆盖时,(x,y)右侧、下方都没有棋子,无法直接一步获胜

2. 间接获胜的情形也是不存在的,是因为上一个必败态(x',y')产生后,接下来考虑的是(x'-1,y'-1)

反过来看,当考虑(x,y)的时候,x行右侧、y列下侧是没有必败态的,

无法转移到必败态,所以当前就是必败态

心得

妙妙cf2300思维题,利用dp的递推以及全图只有min(n,m)个额外的必败态点,将两维拆开

巧妙的通过双指针找到所有额外的必败态点,从而确定n*m个值的sg状态

对于一个n*m的矩形,要么砍掉最后一行,要么砍掉最后一列,要么同时砍掉,去求更小的子局面

代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pb push_back
#define all(a) a.begin(),a.end()
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
std::mt19937_64 gen(std::chrono::system_clock::now().time_since_epoch().count());
ll get(ll l, ll r) { std::uniform_int_distribution<ll> dist(l, r); return dist(gen); }
const int N=2e5+10,INF=0x3f3f3f3f;
int n,m,k,x,y,r[N],c[N];
bool sol(){
	sci(n),sci(m),sci(k);
	rep(i,1,k){
		sci(x),sci(y);
		r[x]=max(r[x],y);
		c[y]=max(c[y],x);
	}
	for(x=n,y=m;x>=1 && y>=1;){
		if(y<=r[x] && x<=c[y])x--,y--;
		else if(y<=r[x])x--;
		else if(x<=c[y])y--;
		else{
			if(x==1 && y==1)return 0;
			x--,y--;
		}
		//printf("x:%d y:%d\n",x,y);
	}
	return 1;
}
int main(){
	puts(sol()?"Chaneka":"Bhinneka");
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值