I.[URAL1540]Battle for the Ring
这大约是我做的第一道SG函数的题(
很容易想到一个区间DP状态:设 \(f_{i,j,k}\) 表示第 \(i\) 条链子,\([j,k]\) 这一段的SG值。
于是我们枚举这一段中删掉了小于等于某个值的元素进行转移。如果删掉的值形成了多个串,依照SG函数的性质,取值应是其异或。
但是这题因为需要保证复杂度在 \(O(kn^3)\) 以内,所以大概不能太随性地转移。我采取的方法是,因为每一个 \(f_{i,j,k}\) 都是一个集合 \(g_{i,j,k}\) 中的 \(\text{mex}\),所以我们考虑由 \(g_{i,j,k-1}\) 推出 \(g_{i,j,k}\),这是可以在 \(O(n)\) 之内解决的。于是复杂度就是严格 \(O(kn^3)\),但是有一大坨细节。如果允许带个 \(\log\),那就可以非常简单地用类珂朵莉树的结构维护,但是我没敢试(
代码:
#include<bits/stdc++.h>
using namespace std;
int n,res;
const int LIM=100;
struct Chain{
int a[110],f[110][110],len,g[110][110],las[110][110],mn[110][110];
bool occ[110];
void read(){scanf("%d",&len);for(int i=1;i<=len;i++)scanf("%d",&a[i]);}
void DP(){
memset(mn,0x3f,sizeof(mn));
for(int i=len;i;i--)for(int j=i;j<=len;j++){
mn[i][j]=min(mn[i][j-1],a[j]);
for(int k=1;k<=LIM;k++){
if(a[j]<=k){g[j][k]=g[j-1][k];continue;}
if(i==j||a[j-1]<=k){las[j][k]=j,g[j][k]=g[j-1][k]^1;continue;}
g[j][k]=g[j-1][k]^f[las[j-1][k]][j-1]^f[las[j-1][k]][j],las[j][k]=las[j-1][k];
}
// printf("(%d %d)\n",i,j);
for(int k=1;k<=LIM;k++)occ[k]=false;
for(int k=mn[i][j];k<=LIM;k++)occ[g[j][k]]=true;
for(int k=0;k<=LIM;k++)if(!occ[k]){f[i][j]=k;break;}
for(int k=1;k<mn[i][j];k++)g[j][k]=f[i][j];
// printf("%d\n",f[i][j]);
}
}
int ALL(){return f[1][len];}
int CHECK(){
res^=f[1][len];
// printf("%d\n",res);
for(int i=1;i<=len;i++){
int tot=0;
for(int j=1,k=1;j<=len;j=k){
if(a[j]<=a[i]){k++;continue;}
k=j;while(k<=len&&a[k]>a[i])k++;
tot^=f[j][k-1];
}
// printf("%d:%d\n",i,tot);
if(tot==res)return i;
}
res^=f[1][len];
return -1;
}
}c[60];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)c[i].read(),c[i].DP(),res^=c[i].ALL();
if(!res){puts("S");return 0;}
puts("G");
for(int i=1;i<=n;i++){
int tmp=c[i].CHECK();
if(tmp==-1)continue;
printf("%d %d\n",i,tmp);
return 0;
}
return 0;
}