前言
三道SG函数的应用题。
都是中文题面,不需要我补充其它信息了吧
棋盘游戏
如果一开始存在一个棋子在两坐标轴上或 y = x y=x y=x 线上,那么先手一定可以一步将它走到终点。
否则,容易发现如果某位玩家某一步将棋子走到了两坐标轴上或 y = x y=x y=x 线上,那么另一位玩家下一步一定可以将这步棋子走到终点,不妨把两坐标轴上或 y = x y=x y=x 线上叫做必败区。非必败区的棋子无法一步走到终点,必然要经过必败区,所以当某位玩家没有别的路可走,下一步必须走进必败区的时候,他就输了。
所以规则可以等价地改为棋子只能在非必败区内移动,不能移动者输。这就是比较标准的Nim游戏了,暴力求出每个位置棋子的SG函数值异或起来即可。
#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=16400;
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[30],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);
putchar(c);
}
inline ll lowbit(ll x){return x&-x;}
int n,ans;
bool po[MAXN];
int sg[105][105];
signed main()
{
for(int i=1;i<100;i++)
for(int j=1;j<100;j++){
if(i==j)continue;
for(int k=1;k<=i;k++){
if(i-k==0||i-k==j)continue;
po[sg[i-k][j]]=1;
}
for(int k=1;k<=j;k++){
if(j-k==0||j-k==i)continue;
po[sg[i][j-k]]=1;
}
for(int k=1;k<=min(i,j);k++){
if(i-k==0||j-k==0)continue;
po[sg[i-k][j-k]]=1;
}
while(po[sg[i][j]])sg[i][j]++;
for(int k=1;k<=i;k++){
if(i-k==0||i-k==j)continue;
po[sg[i-k][j]]=0;
}
for(int k=1;k<=j;k++){
if(j-k==0||j-k==i)continue;
po[sg[i][j-k]]=0;
}
for(int k=1;k<=min(i,j);k++){
if(i-k==0||j-k==0)continue;
po[sg[i-k][j-k]]=0;
}
}
for(int T=read();T--;){
n=read(),ans=0;
for(int i=1;i<=n;i++){
int x=read(),y=read();
if(x==0||y==0||x==y)ans=-1;
else ans^=sg[x][y];
}
printf(ans?"^o^\n":"T_T\n");
}
return 0;
}
[HNOI2014]江南乐
对于操作若干堆石子、不能移动者输的公平游戏,都可以对每堆石子求出SG函数值然后异或。这道题显然也可以这么做。
按照这题的定义,暴力求SG函数仿佛是 O ( n 2 ) O(n^2) O(n2) 的。然而,在求 m e x mex mex 的过程中,我们求了很多次重复信息,例如将数量为 x x x 的石子分为 m m m 堆,其中有 x m o d m x\bmod m xmodm 堆数量为 ⌊ x m ⌋ + 1 \lfloor\frac{x}{m}\rfloor+1 ⌊mx⌋+1 的石子,剩余都是数量为 ⌊ x m ⌋ \lfloor\frac{x}{m}\rfloor ⌊mx⌋ 的石子。这时我们求的SG函数值只与 ⌊ x m ⌋ \lfloor\frac{x}{m}\rfloor ⌊mx⌋ 的值以及 m m m、 x m o d m x\bmod m xmodm 的奇偶性有关,故在所有 ⌊ x m ⌋ \lfloor\frac{x}{m}\rfloor ⌊mx⌋ 相同的 m m m 中只需要计算最大的两个即可。
用整除分块可以 O ( n n ) O(n\sqrt{n}) O(nn) 解决,只是常数有亿点大(20532ms)。
#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=140005;
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[30],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);
putchar(c);
}
int n,T,F,a[MAXN],ans;
bool vis[MAXN],po[MAXN];
int sg[MAXN];
inline void getsg(int x){
sg[x]=0;
for(int i=2,j,la;i<=x;i=la+1){
j=x/i,la=x/j;
int k=x%j;
for(int o=la;o>=i&&o>la-2;o--){
int p=k+(la-o)*j,q=0;
if(p>=o)break;
if(p&1)q^=sg[j+1];
if((o-p)&1)q^=sg[j];
po[q]=1;
}
}
while(po[sg[x]])sg[x]++;
for(int i=2,j,la;i<=x;i=la+1){
j=x/i,la=x/j;
int k=x%j;
for(int o=la;o>=i&&o>la-2;o--){
int p=k+(la-o)*j,q=0;
if(p>=o)break;
if(p&1)q^=sg[j+1];
if((o-p)&1)q^=sg[j];
po[q]=0;
}
}
}
signed main()
{
T=read(),F=read();
for(int i=F;i<=100000;i++)getsg(i);
while(T--){
n=read(),ans=0;
for(int i=1;i<=n;i++)a[i]=read(),ans^=sg[a[i]];
print(ans>0,T?' ':'\n');
}
return 0;
}
[CQOI2013]新Nim游戏
首先想到,一回合过后就是最经典的Nim游戏,所以只要确保对手在第一局不可能搞出一个石子数异或和为0的局面即可。
首先明白一件事:先手必胜。
这很好证明,先手只要拿到只剩下一堆石子即可。
然后发现,如果第一局先手操作过后,石子集合中存在一个非空子集的异或和为0,那么一定输。所以我们要求的就是如何花费删去最少数量的石子,使得不存在任何非空子集异或和为0。
先猜后证:结论应该是如果遇到一个极小的异或和为0的非空子集,至少要拿走一个,那就拿最小的那个。
感性证明:拿走较大的不会更优,因为在拿走任何一个的情况下,原子集里的所有数都仍可以被凑出来,不会影响其它异或和为0的极小非空子集内的决策。
然后只要把数降序排序,用线性基就可以了。
#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=105;
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[30],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);
putchar(c);
}
int n;
ll a[MAXN],tb[35],ans;
signed main()
{
n=read();
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+1+n,[](ll x,ll y){return x>y;});
for(int i=1;i<=n;i++){
bool ok=0;
int x=a[i];
for(int j=30;j>=0&&x>0;j--)if((x>>j)&1){
if(tb[j]>0)x^=tb[j];
else tb[j]=x,x=0,ok=1;
}
if(!ok)ans+=a[i];
}
print(ans);
return 0;
}