题目
一个树上删边的博弈 和
对于每一个结点 u其子结点把他结点化 也就是从叶子结点开始把他的异或和求出来逐个增加
也就是说对于那些1 来说就是正常操作
对于那些不是1的来说 直接 sg^(w%2)也就是偶数不变 奇数 就相当于 旁边多了一个1 结点
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;
struct edge
{
int v,w,next;
};
edge no[maxn*2];
int head[maxn];
int cnt;
int t=0;
int sg[maxn];
void init()
{
cnt=1;
memset(head,0,sizeof(head));
memset(sg,0,sizeof(sg));
}
void add(int x,int y,int w)
{
no[cnt].v=y;
no[cnt].w=w;
no[cnt].next=head[x];
head[x]=cnt;
cnt++;
}
void dfs(int u,int fa)
{
for(int i=head[u];i;i=no[i].next)
{
int v=no[i].v;
int w=no[i].w;
if(v==fa)
continue;
dfs(v,u);
if(w==1)
sg[u]^=(sg[v]+1);
else
sg[u]^=(sg[v]^(w%2));
}
}
void solve()
{
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(0,-1);
if(sg[0]==0)
cout<<"Case "<<t<<": "<<"Jolly"<<"\n";
else
cout<<"Case "<<t<<": "<<"Emily"<<"\n";
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--)
{
t++;
init();
solve();
}
return 0;
}
题目
把每一列的棋子间隔(可以走的)看成Nim博弈中的石子数量,那么答案就所有差的异或…
经典的nim
sg[i]=x;
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
int t=0;
int a[maxn];
int b[maxn];
void init()
{
}
void solve()
{
int sum=0;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
for(int i=1;i<=n;i++)
{
sum^=(b[i]-a[i]-1);
}
if(sum==0)
cout<<"Case "<<t<<": black wins"<<endl;
else
cout<<"Case "<<t<<": white wins"<<endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--)
{
t++;
init();
solve();
}
return 0;
}
题目
nim博弈, 将灰色和白色棋子之间的距离看作是石头的数量, 可以转化成简单的nim博弈, 直接以后求解。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
int t=0;
int a[maxn];
int b[maxn];
void init()
{
}
void solve()
{
int n;
int sum=0;
cin>>n;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
sum^=abs(y-x-1);
}
if(sum==0)
cout<<"Case "<<t<<": "<<"Bob"<<endl;
else
cout<<"Case "<<t<<": "<<"Alice"<<endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--)
{
t++;
init();
solve();
}
return 0;
}
题意:
有k堆石子,Alice先手,每个人每次可以任选一堆石子取1 ~ ai个石子。取到最后一个石子的输。
思路:
先考虑一种特殊情况,就是所有石子都是1的时候,这种情况下谁赢谁输已经确定了。
剩下的情况,如果改为取到最后一个石子的获胜的情况的话,就是典型的尼姆博弈了。然而这里很巧的是Nim博弈的结论对这题同样适用,因为Nim博弈的获胜方总能将一个石子留给另外一个人。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
int t = 0;
void init() {
}
void solve() {
ll sum = 0;
int flag = 0;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x > 1)
flag = 1;
sum ^= x;
}
if (!flag) {
if (n % 2 != 0)
cout << "Case " << t << ": " << "Bob" << endl;
else
cout << "Case " << t << ": " << "Alice" << endl;
return;
}
if (sum == 0)
cout << "Case " << t << ": " << "Bob" << endl;
else
cout << "Case " << t << ": " << "Alice" << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _ = 1;
cin >> _;
while (_--) {
t++;
init();
solve();
}
return 0;
}
题意:r*c方格中,每个格子有一定石子,每次移动每格任意数量石子,只能向下或者向右动一格,不能移动为败
思路:显然是Nim,到右下曼哈顿距离为偶数的不用管,因为先手动一下后手动一下最后移到右下后还是先手的回合;奇数移动一格必到偶数格,所以奇数的Nim一下。很简单的入门题。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
int t = 0;
void init() {
}
void solve() {
ll sum = 0;
int n,m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <=m ; ++j) {
int x;
cin >> x;
if((n-i+m-j)%2==0)
continue;
sum ^= x;
}
}
if (sum == 0)
cout << "Case " << t << ": " << "lose" << "\n";
else
cout << "Case " << t << ": " << "win" << "\n";
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _ = 1;
cin >> _;
while (_--) {
t++;
init();
solve();
}
return 0;
}
题意
有n堆石子,双方轮流进行操作,Alice先手,每次操作可以把任意一堆石子分成两堆不同的石子,谁不能进行操作谁输
思路
这题符合用SG的要求,所以可以先打出一堆为10000的石子的SG值然后异或即可,那么怎么进行操作呢,因为这个不是把石子取走,所以每次操作会留下两个后继状态,这两个后继状态同样可以用SG表示所以上一状态的SG值就为两个后继状态的SG异或值,对于一堆有i个石子的状态他的后继状态可以由
{ ( 1 , 1 − i ) , ( 2 , i − 2 ) , ( 3 , i − 3 ) ⋯ ( ( i − 1 ) / 2 , i − ( i − 1 ) / 2 ) } { (1,1-i),(2,i-2),(3,i-3)\cdots((i-1)/2,i-(i-1)/2)}
{(1,1−i),(2,i−2),(3,i−3)⋯((i−1)/2,i−(i−1)/2)}
所以有
S G ( x ) = m e x { S G ( 1 ) ∧ S G ( x − 1 ) , S G ( 2 ) ∧ S G ( x − 2 ) , . . . , S G ( ( x − 1 ) / 2 ) ∧ S G ( x − ( x − 1 ) / 2 ) } SG(x) = mex{ SG(1)\wedge SG(x-1), SG(2)\wedge SG(x-2),…, SG((x-1)/2)\wedge SG(x-(x-1)/2)}
SG(x)=mex{SG(1)∧SG(x−1),SG(2)∧SG(x−2),…,SG((x−1)/2)∧SG(x−(x−1)/2)}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 5;
int sg[maxn];
int has[maxn];
int t = 0;
void init() {
memset(sg, 0, sizeof(sg));
}
void getSG() {
init();
for (int i = 3; i < maxn; i++) {
memset(has, 0, sizeof(has));
for (int j = 1; j <= (i - 1) / 2; ++j) {
has[sg[j] ^ sg[i - j]] = 1;
}
for (int j = 0;; j++) {
if (has[j] == 0) {
sg[i] = j;
break;
}
}
}
}
void solve() {
int n;
cin >> n;
int sum = 0;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
sum ^= sg[x];
}
if (sum == 0)
cout << "Case " << t << ": " << "Bob"<<endl;
else
cout << "Case " << t << ": " << "Alice"<<endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
getSG();
int _ = 1;
cin >> _;
while (_--) {
t++;
solve();
}
return 0;
}
题意:给你一串含“.”和“X”的字串,每次一个玩家可以把‘."变成“X”,谁先弄到三个XXX就赢。假如先手必赢,输出所有能必赢的第一步,否则输出0。
思路:显然如果一个X周围两格有X那么肯定能一步变成XXX,所以两个人都要避免在自己回合产生这种情况。如果一开始就存在上述情况,那么肯定是那一步。否则我遍历每一个空格看看能不能下这一步。满足我在这个空格变成“X”不会造成上述情况,然后算出nim和是否留给对手一个必败态。
设sg[x]表示长度为x的空格的sg函数,然后我遍历1~x位置变成“X”,那么空格键会被我分成两块(比如…我在3位置下X,那么空格被我分成了左0右0两块,注意X旁边两块不能动)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[205];
int sg[205];
int has[205];
int pos;
string s;
int t=0;
void init()
{
pos=0;
}
void getSG()
{
sg[0]=0;
for(int i=1;i<205;i++)
{
memset(has,0,sizeof(has));
for(int j=1;j<=i;j++)
{
int t=0;
if(j>=3)
{
t^=sg[j-3];
}
if(i-j>=2)
{
t^=sg[i-j-2];
}
has[t]=1;
}
for(int j=0;;j++)
{
if(has[j]==0)
{
sg[i]=j;
break;
}
}
}
}
bool check()///后手必败
{
int sum=0;
int len=s.size();
for(int i=0;i<len;i++)
{
if(s[i]=='X')
{
if((i-1>=0&&s[i-1]=='X')||(i-2>=0&&s[i-2]=='X')||(i+1<len&&s[i+1]=='X')||(i+2<len&&s[i+2]=='X'))
return false;
}
}
int num=0;
for(int i=0;i<len;i++)
{
if(s[i]=='X')
{
if(i-num-1>=0&&s[i-num-1]=='X')
num-=2;
num-=2;
if(num>0)
sum^=sg[num];
num=0;
}
else
num++;
}
if(len-num-1>=0&&s[len-num-1]=='X')
num-=2;
if(num>0)
sum^=sg[num];
return sum==0;
}
void solve()
{
cin>>s;
int len=s.size();
for(int i=0;i<len;i++)
{
if (s[i]=='.')
{
s[i]='X';
if(i+1<len&&i-1>=0&&s[i+1]=='X'&&s[i-1]=='X')
{
a[++pos]=i+1;
}
else if(i+1<len&&i+2<len&&s[i+1]=='X'&&s[i+2]=='X')
{
a[++pos]=i+1;
}
else if(i-1>=0&&i-2>=0&&s[i-1]=='X'&&s[i-2]=='X')
{
a[++pos]=i+1;
}
else if(check())
{
a[++pos]=i+1;
}
s[i]='.';
}
}
cout<<"Case "<<t<<": ";
if(pos==0)
{
cout<<0<<endl;
}
else
{
for(int i=1;i<=pos;i++)
{
cout<<a[i];
if(i!=pos)
cout<<" ";
}
cout<<endl;
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
getSG();
int _=1;
cin>>_;
while(_--)
{
t++;
init();
solve();
}
return 0;
}
题意:n堆石头,最少拿每堆石头的一个,最多拿每堆石头的一半,问最后谁赢
SG函数打表,但是打了表我也没找出什么规律…还以为打错表了,看了看题解没错,就是没找出规律…
规律就是当这个数是偶数的时候,它的SG值等于它除以2,如果是奇数的话,它的SG值就等于SG【x/2】
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;
int t=0;
void init()
{
}
int getSG(int x)
{
if(x==0)
return 0;
if(x==1)
return 0;
if(x%2!=0)
return getSG((x-1)/2);
else
return x/2;
}
void solve() {
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
sum^=getSG(x);
}
cout<<"Case "<<t<<": ";
if(sum==0)
cout<<"Bob"<<endl;
else
cout<<"Alice"<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
t++;
solve();
}
return 0;
}
题意:有n个骑士(1<=n<=1000)在无限的棋盘中,给定n个骑士的坐标(xi,yi),(0<=xi,yi<500)。
骑士每一步有六种走法,最后不能移动骑士的算输,问先手胜还是后手胜。
思路:n个骑士相互独立,所以可以应用SG定理。
其一,注意到骑士总是往左边走,并且,是在当前对角线的左边,所以在计算每一格的SG函数时可以按照row+col为定值依次计算。
其二,注意到骑士只有六种走法,所以对于每一格的SG函数值SG(x,y)不会超过6。
注意数组越界的问题
#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma GCC optimize(3,"Ofast","inline")
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=2000;
int sg[maxn][maxn];
int t=0;
int dir[6][2] = { {-2,1}, {1,-2},{-2,-1},{-1,-2},{-3,-1},{-1,-3}};
int getSG(int a,int b)
{
if(sg[a][b]!=-1)
return sg[a][b];
int has[10]={0};
int x,y;
for(int i=0;i<6;i++)
{
x=a+dir[i][0];
y=b+dir[i][1];
if(x>=0&&y>=0)
has[getSG(x,y)]=1;
}
for(int i=0;;i++)
{
if(has[i]==0)
return sg[a][b]=i;
}
}
void solve() {
int n;
int sum=0;
cin>>n;
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
sum^=sg[a][b];
}
cout<<"Case "<<t<<": ";
if(sum==0)
cout<<"Bob"<<"\n";
else
cout<<"Alice"<<"\n";
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(sg,-1,sizeof(sg));
for(int i=0;i<500;i++)
{
for(int j=0;j<500;j++)
{
sg[i][j]=getSG(i,j);
}
}
int _=1;
cin>>_;
while(_--){
t++;
solve();
}
return 0;
}
题意
现在有几串手镯,每个手镯上都有一些珠子,每个珠子都有权值,Aladdin 和 Genie 轮流取珠子,当取了一颗珠子后,这个手镯上所有权值大于等于这颗珠子权值的珠子,都要被删去。因此,这个手镯就会变成新的几个手镯(因为被切割了)。
如,5-1-7-2-4-5-3 这串手镯,选第一颗珠子,权值为5,因此,5,7,5 就要被删去。手镯变成了新的 1,2-4,3 三个手镯。
题解
每个手镯都是独立的,因此可以异或每个手镯的 sg 值求解。而每个手镯,可以在某些结点被取走珠子,变成新的几段,任意一种情况的 sg 值,便是新分成的几段的 sg 值异或起来。再将每一种情况的 sg 值,记录在 vis 中,查找没出现过的最小的值,便是这个手镯的 sg 值。有点绕,我写的时候也有点迷了。
因为范围较小,可直接暴力查找每一段的 sg 值。用记忆化搜索,重复找过的就不找了。sg[i][j] 代表当前这个手镯,左端点下标为 i ,右端点下标为 j ,当找过这一段的 sg 值时,便直接返回不再重复找。
下面举一个例子…我们求 sg[1][4],即左端点下标为 1,右端点下标为 4:
最后的结果,是将每个手镯的 sg 值异或,若为 0 ,则后手赢,否则先手赢。
若先手赢,还需输出第一次赢怎么取。
换位思考一下即可。我们需要先手取完,后手取的时候,面临一个所有手镯的 sg 值异或为 0 的情况(这样的情况是后手赢,此时后手是先手)。
因此,直接暴力枚举每个手镯的每个珠子。看取完后,原本的手镯的 sg值异或,再异或被选中的这个手镯,取走一部分珠子后的 sg 值。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=55;
int mm[maxn][maxn];
int sg[maxn][maxn];
int t=0;
int Sg[maxn];
int num[maxn];
int res[maxn][maxn];///第i串珠子 拿走第j值得SG
vector<pair<int,int> >vv;
void init()
{
vv.clear();
}
bool cmp(pair<int,int> a,pair<int,int> b)
{
if(a.first==b.first)
return a.second<b.second;
return a.first<b.first;
}
int getSG(int now,int l,int r)///第now串 l-r 的SG值
{
if(l>r)
return 0;
if(l==r)
return sg[l][r]=1;
if(sg[l][r]!=-1)
return sg[l][r];
int has[maxn]={0};
for(int i=l;i<=r;i++)///选第i珠子
{
int t=0;
int xx=mm[now][i];
int star=l;
for(int j=l;j<=r;)
{
while(star<=r&&mm[now][star]>=xx)
{
star++;
}
j=star;
while(j<=r&&mm[now][j]<xx)
{
j++;
}
t^=getSG(now,star,j-1);
star=j;
}
has[t]=1;
if(l==1&&r==num[now])
res[now][i]=t;
}
for(int i=0;;i++)
{
if(has[i]==0)
{
return sg[l][r]=i;
}
}
}
void solve() {
int sum=0;
int k;
cin>>k;
for(int i=1;i<=k;i++)
{
memset(sg,-1,sizeof(sg));
cin>>num[i];
int n=num[i];
for(int j=1;j<=n;j++)
{
cin>>mm[i][j];
}
Sg[i]=getSG(i,1,n);
//Sg[i]=sg[1][n];
sum^=Sg[i];
}
cout<<"Case "<<t<<": ";
if(sum==0)
{
cout<<"Genie"<<endl;
}
else
{
cout<<"Aladdin"<<endl;
for(int i=1;i<=k;i++)
{
for(int j=1;j<=num[i];j++)
{
if((sum^Sg[i]^res[i][j])==0)
{
//cout<<"("<<i<<" "<<mm[i][j]<<")";
vv.push_back({i,mm[i][j]});
}
}
}
sort(vv.begin(),vv.end(),cmp);
int cnt=(int)(unique(vv.begin(),vv.end())-vv.begin());
for(int i=0;i<cnt;i++)
cout<<"("<<vv[i].first<<" "<<vv[i].second<<")";
cout<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
t++;
init();
solve();
}
return 0;
}
题意:
一开始没看懂题,理解错了题面,其实需要根据给出的字符串判断,现在该谁操作了,如,X 和 O 加起来,如果有偶数个,那么现在该 Alice 操作,否则该电脑操作。
SG打表
用一个三维数组,sg[n][i][j] 来表示
最后的结果,是将每个手镯的 sg 值异或,若为 0 ,则后手赢,否则先手赢。
若先手赢,还需输出第一次赢怎么取。
换位思考一下即可。我们需要先手取完,后手取的时候,面临一个所有手镯的 sg 值异或为 0 的情况(这样的情况是后手赢,此时后手是先手)。
因此,直接暴力枚举每个手镯的每个珠子。看取完后,原本的手镯的 sg值异或,再异或被选中的这个手镯,取走一部分珠子后的 sg 值。
#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma GCC optimize(3,"Ofast","inline")
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=105;
int sg[maxn][3][3];
int t=0;
int getSG(int cnt,int l,int r)
{
if(cnt==0)
return 0;
if(sg[cnt][l][r]!=-1)
return sg[cnt][l][r];
int has[2*maxn]={0};
for(int i=1;i<=cnt;i++)
{
if((i==1&&l==1)||(i==cnt&&r==1))
{
}
else
{
int t=0;
t^=getSG(i-1,l,1);
t^=getSG(cnt-i,1,r);
has[t]=1;
}
if((i==1&&l==2)||(i==cnt&&r==2))
{
}
else
{
int t=0;
t^=getSG(i-1,l,2);
t^=getSG(cnt-i,2,r);
has[t]=1;
}
}
for(int i=0;;i++)
{
if(has[i]==0)
{
return sg[cnt][l][r]=i;
}
}
}
void solve() {
int flag=0;
string s;
cin>>s;
int l=0,r=0;
int sum=0;
int num=0;
int len=s.size();
for(int i=0;i<len;i++)
{
if(s[i]=='X')
{
flag=!flag;
r=1;
sum^=sg[num][l][r];
num=0;
l=1;
r=0;
}
else if(s[i]=='O')
{
flag=!flag;
r=2;
sum^=sg[num][l][r];
num=0;
l=2;
r=0;
}
else
num++;
}
if(num>0)
{
sum^=sg[num][l][0];
}
cout<<"Case "<<t<<": ";
if((flag==0&&sum==0)||(flag==1&&sum!=0))
{
cout<<"No"<<endl;
}
else
{
cout<<"Yes"<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(sg,-1,sizeof(sg));
for(int i=0;i<maxn;i++)
{
for(int l=0;l<3;l++)
{
for(int r=0;r<3;r++)
{
sg[i][l][r]=getSG(i,l,r);
}
}
}
int _=1;
cin>>_;
while(_--){
t++;
solve();
}
return 0;
}