【题解】弃疗Nim (2019,5.23)

Description

Sample Input

5 1

2 4 2 2

Sample Output

1

1

0

1

0

Solution

这题可以直接考虑暴力,时限也给了 3s 那么简直是给暴力一条活路啊。

开一个二维数组 \(CanBeGet[i][j]\) 表示石子个数在 \(i\) 时可以取走 \(j\)

暴力直接 \(O(N^3)\) 暴力把 \(CanBeGet\) 更新(Xor)一下。

最后统计答案的时候只要看看当前状态转移出去的是否有必输状态,有的话当前状态为胜。

细节就是把取一个也给附上初值。

我们考虑怎么优化这个鬼东西。

我们发现他要给一段区间 Xor 。

跟位运算有关系的东西我们都得想想状压,但是发现这题数据范围有 1e5 。。。。

那只能用bitset了。

我们记一个bitset,假设叫 \(f[i]\) ,表示当前石子个数能否取到剩下 \(i\)

这样每次变成一个新状态时就是要把之前的bitset左移一格,表示可以取的石子个数没变,但是剩下的石子数量变多了。

同时我们的这个bitset还要异或上当前这个石子堆大小可以取得区间的bitset。

可以取得区间就是区间全部为一的区间,可以用bitset快速做。

最后只要判断一下后继是否可以取到负的局面。

我用 \(ans[i]\) 保存答案。 如果 \(ans[i]==1\) 那么这就是必败的。

所以只要判断后继是否有可以取到的,同时满足是 \(ans\)\(1\) ,那么当前状态就是必胜态,只有当后继没有必败态是才是败态。

骗分代码参考 \(subtask1\)

暴力代码参考 \(subtask2\)

正解代码参考 \(subtask3\)

#include<bits/stdc++.h>
using namespace std;
#define A first 
#define B second

typedef pair<int,int> PII;
int n,m;
const int N=1005;
int SG[N],CanBeGet[N][N];

namespace subtask1{
	inline void solve(){
		for(int i=1;i<=n;++i) printf("%d\n",i&1);
	}
}

namespace subtask2{
	inline void solve(){
		for(int i=1;i<=m;++i){
			int a=0,b=0,c=0,d=0;
			scanf("%d%d%d%d",&a,&b,&c,&d);
			for(int j=a;j<=b;++j) for(int k=c;k<=d;++k)
				CanBeGet[j][k]^=1;
		}
		for(int i=1;i<=n;++i) CanBeGet[i][1]=1;
		for(int i=1;i<=n;++i) for(int j=1;j<=min(i,N-5);++j)
			if(CanBeGet[i][j]){
				if(i==j) SG[i]=1;
				else SG[i]|=(!SG[i-j]);
			}
		for(int i=1;i<=n;++i) printf("%d\n",SG[i]);
	}
}

namespace subtask3{
	const int M=1e5+5;
	vector <PII> v[M];
	bitset<M> f,ans,BASE;

	inline bitset<M> paint(int l,int r){
		BASE.set();
		BASE<<=l;
		BASE^=BASE<<(r-l+1);
		return BASE;
	}

	inline void solve(){
		for(int i=1;i<=m;++i){
			int a=0,b=0,c=0,d=0;
			scanf("%d%d%d%d",&a,&b,&c,&d);
			v[a].push_back(make_pair(c,d));
			v[b+1].push_back(make_pair(c,d));
		}
		ans[0]=1;
		// f[i] 表示从当前的石子个数可以转移到石子个数为i的石子状态
		for(int i=1;i<=n;++i){
			f<<=1;f[i-1]=1;// 可以取得石子变多了,那么我们能剩下的就变多了,刚好比之前多1,
			for(int j=0;j<(int)v[i].size();++j) f^=paint(i-v[i][j].B,i-v[i][j].A);// 取出一段为1的区间
			if(!(f&ans).count()) ans[i]=1;
 		}
		for(int i=1;i<=n;++i) printf("%d\n",~ans[i]);
	}
}

int main(){
	scanf("%d%d",&n,&m);
	if(m==0) return subtask1::solve(),0;
	if(n<=1000) return subtask2:: solve(),0;
	else subtask3::solve();
	return 0;
}

posted @ 2019-05-24 14:42  章鱼那个哥  阅读(143)  评论(0编辑  收藏  举报