【博弈论】【SG函数】【线段树】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Thursday, September 1, 2016 Problem H. Cups and Beans

一开始有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;
}
posted @ 2017-08-16 23:52  AutSky_JadeK  阅读(302)  评论(0编辑  收藏  举报
TVアニメ「Charlotte(シャーロット)」公式サイト TVアニメ「Charlotte(シャーロット)」公式サイト