一开始有n个杯子,每个杯子里有一些豆子,两个人轮流操作,每次只能将一个豆子移动到其所在杯子之前的某个杯子里,不过可以移动到的范围只有一段区间。问你是否先手必胜。
一个杯子里的豆子全都等价的,因为sg函数是异或起来的值,所以一个杯子里如果有偶数个豆子,就没有意义。
用sg(i)表示i杯子中的豆子的sg值,sg(i)就是其所能移动到的那段杯子的区间的sg值的mex(未出现的最小非负整数)。可以用线段树去做。是经典问题。
由于每次看似是后缀询问,实则是全局询问,故而主席树完全是多余的。
回顾一下区间mex的求法:
主席树里每个值的位置存当前该值出现的最右位置。
如果root[r]的前缀主席树中,某值最右位置大于等于l,说明该值出现在了l,r中。
所以主席树维护区间最小值,如果左半值域的最小值<l,则说明左半值域有值未在l,r出现,则查询左子树;否则查询右子树。
#include<cstdio> #include<algorithm> using namespace std; #define N 100010 struct Node{int v,lc,rc;}T[N*22]; int root[N],e; void Insert(int pre,int cur,int p,int v,int l,int r) { if(l==r) { T[cur].v=v; return; } int m=(l+r>>1); if(p<=m) { T[cur].lc=++e; T[cur].rc=T[pre].rc; Insert(T[pre].lc,T[cur].lc,p,v,l,m); } else { T[cur].rc=++e; T[cur].lc=T[pre].lc; Insert(T[pre].rc,T[cur].rc,p,v,m+1,r); } T[cur].v=min(T[T[cur].lc].v,T[T[cur].rc].v); } int Goal; int Query(int R,int l,int r) { if(l==r) return l; int m=(l+r>>1); if(T[T[R].lc].v<Goal) return Query(T[R].lc,l,m); else return Query(T[R].rc,m+1,r); } int n,a[N],C[N],anss[N]; int main() { // freopen("h.in","r",stdin); int x,y; scanf("%d",&n); root[1]=++e; Insert(root[0],root[1],1,1,1,100001); for(int i=1;i<n;++i) { scanf("%d%d",&C[i],&a[i]); Goal=i+1-C[i]; anss[i]=Query(root[i],1,100001)-1; root[i+1]=++e; Insert(root[i],root[i+1],anss[i]+1,i+1,1,100001); } int ans=0; for(int i=1;i<n;++i){ if(a[i]&1){ ans^=anss[i]; } } puts(ans ? "First" : "Second"); return 0; }
时间: 2024-10-06 10:16:40