12.28模拟赛

T1:

本题其实不算太简单

xor前缀差分,转化为[l,r]中选择两个数xor最大(边界的l-1再考虑一下)

按位贪心显然,区间取trie可持久化即可

考虑到一个区间的取法,是从左边答案,右边答案,和左右各拿一个。

线段树可能区间合并长度过长,复杂度无法保证

分块?复杂度有保证!

n,q范围也比较有趣

mx[i][j]记录块i到块j的答案,可以O(nsqrt(n))暴力处理

然后查询的时候边角处理一下即可。

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=12000+5;
const int blo=115;
struct trie{
    int ch[2];
    int sz;
}t[N*32+N+N];
int rt[N];
int tot;
int n,m;
int a[N],b[N];//b is presum
int mx[blo][blo];
int L[N],R[N];
int be[N];
void ins(int x,int y,int c){
    rt[x]=++tot;
    t[rt[x]].sz=t[rt[y]].sz+1;
    x=rt[x],y=rt[y];
    for(reg i=30;i>=0;--i){
        ++tot;
        if(c&(1<<i)){
            t[x].ch[1]=tot;
            t[x].ch[0]=t[y].ch[0];
            x=t[x].ch[1];y=t[y].ch[1];
            t[x].sz=t[y].sz+1;
        }else{
            t[x].ch[0]=tot;
            t[x].ch[1]=t[y].ch[1];
            x=t[x].ch[0];y=t[y].ch[0];
            t[x].sz=t[y].sz+1;
        }
    }
}
int query(int x,int y,int c){
    x=rt[x-1],y=rt[y];
    int ret=0;
    for(reg i=30;i>=0;--i){
        if(c&(1<<i)){
            int siz=t[t[y].ch[0]].sz-t[t[x].ch[0]].sz;
            if(siz){
                ret+=(1<<i);
                x=t[x].ch[0];
                y=t[y].ch[0];
            }else{
                x=t[x].ch[1];
                y=t[y].ch[1];
            }
        }else{
            int siz=t[t[y].ch[1]].sz-t[t[x].ch[1]].sz;
            if(siz){
                ret+=(1<<i);
                x=t[x].ch[1];
                y=t[y].ch[1];
            }else{
                x=t[x].ch[0];
                y=t[y].ch[0];
            }
        }
    }
    return ret;
}
int main(){
    rd(n);rd(m);
    for(reg i=1;i<=n;++i){
        rd(a[i]);b[i]=b[i-1]^a[i];
        
        be[i]=(i-1)/blo+1;
        if(be[i]!=be[i-1]) L[be[i]]=i;
        R[be[i]]=max(R[be[i]],i);
    }
    int cnt=be[n];
    for(reg i=1;i<=n;++i){
        ins(i,i-1,b[i]);
    }//cout<<" -------------tot "<<tot<<" "<<cnt<<endl;
    
    for(reg i=1;i<=cnt;++i){
        for(reg j=L[i]+1;j<=R[i];++j){
            mx[i][i]=max(mx[i][i],query(L[i],j-1,b[j]));
        }
    }
    for(reg i=1;i<=cnt;++i){
        for(reg j=i+1;j<=cnt;++j){
            mx[i][j]=max(mx[i][j-1],mx[j][j]);
            for(reg k=L[j];k<=R[j];++k){
                mx[i][j]=max(mx[i][j],query(L[i],k-1,b[k]));
            }
        }
    }
    int las=0;
    int l,r,x,y;
    
    while(m--){
        rd(x);rd(y);
        x%=n;y%=n;
        l=min((x+las)%n+1,(y+las)%n+1);
        r=max((x+las)%n+1,(y+las)%n+1);
        //cout<<" l r "<<l<<" "<<r<<endl;
        int ans=0;
        if(be[l]==be[r]){
            for(reg i=l+1;i<=r;++i){
                ans=max(ans,query(l,i-1,b[i]));
            }
        }else{
            ans=max(ans,mx[be[l]+1][be[r]-1]);
            for(reg i=l;i<=R[be[l]];++i){
                ans=max(ans,query(l+1,r,b[i]));
            }
            for(reg i=L[be[r]];i<=r;++i){
                ans=max(ans,query(l,i-1,b[i]));
            }
        }
        ans=max(ans,query(l,r,b[l-1]));
        printf("%d
",ans);
        las=ans%n;
    }
    return 0;
}

}
signed main(){
//    freopen("xor.in","r",stdin);
//    freopen("xor.out","w",stdout);
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/28 8:09:56
*/
View Code

本题不错!

看似一个AC自动机,,,

诶,字符集太大?暴力跳fail直接扫帚图卡飞

考虑一般的AC自动机怎么做?

有儿子的找fail[x]即可

没有儿子进行ch[x][i]=ch[fail[x]][i]路径压缩!

这个路径压缩保证了第一步找儿子的复杂度是O(1)的

或者说,这里把fail的边继承了下来!

什么东西可以路径压缩,继承之前的信息呢?

可持久化!!

更新完儿子的fail之后,对自己在fail[x]根的基础上,建造自己的线段树即可。

注意:

先把rt[x]变成rt[fail[x]]直接继承,然后再建造

否则trie的叶子没有儿子,就没有根了。。。

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define mid ((l+r)>>1)
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=200000+5;
const int M=200000;
struct node{
    int nxt,to,v;
}e[2*N];
int hd[N],tot;
void add(int x,int y,int z){
    e[++tot].nxt=hd[x];
    e[tot].v=z;
    e[tot].to=y;
    hd[x]=tot;
}
int fa[N];
int fail[N];
int n;
queue<int>q;
struct po{
    int id;
    int ls,rs;
}t[N*20];
int rt[N];
int cnt;
void upda(int &x,int y,int l,int r,int c,int d){
    if(!x) x=++cnt;
    if(l==r){
        t[x].id=d;return;
    }
    if(c<=mid) {
        t[x].rs=t[y].rs;
        upda(t[x].ls,t[y].ls,l,mid,c,d);
    }else{
        t[x].ls=t[y].ls;
        upda(t[x].rs,t[y].rs,mid+1,r,c,d);
    }
}
int query(int x,int l,int r,int c){
    if(!x) return 1;
    if(l==r) return t[x].id;
    if(c<=mid) return query(t[x].ls,l,mid,c);
    return query(t[x].rs,mid+1,r,c);
}
void AC(){
    
    for(reg i=hd[1];i;i=e[i].nxt){
        int y=e[i].to;
        int tmp=rt[1];rt[1]=0;
        upda(rt[1],tmp,1,M,e[i].v,y);
        //cout<<" st "<<y<<endl;
        fail[y]=1;q.push(y);
    }
    while(!q.empty()){
        int x=q.front();q.pop();
        //cout<<" xx "<<x<<" "<<fail[x]<<endl;
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            //cout<<" to "<<y<<" "<<e[i].v<<endl;
            fail[y]=query(rt[fail[x]],1,M,e[i].v);
            q.push(y);
        }
        int tmp=rt[fail[x]];
        rt[x]=rt[fail[x]];
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            rt[x]=0;
            upda(rt[x],tmp,1,M,e[i].v,y);
            tmp=rt[x];
        }
    }
}
int main(){
    rd(n);
    for(reg i=2;i<=n+1;++i){
        rd(fa[i]);
        ++fa[i];
    }int x;
    for(reg i=2;i<=n+1;++i){
        rd(x);
        add(fa[i],i,x);
    }
    
    AC();
    for(reg i=2;i<=n+1;++i){
        //if(fail[i]==1) fail[i]=0;
        if(fail[i]==0) fail[i]=1;
        printf("%d ",fail[i]-1);
    }
    return 0;
}

}
signed main(){
//    freopen("data.in","r",stdin);
//    freopen("std.out","w",stdout);
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/28 9:22:51
*/



    
View Code

本题利用了可持久化的继承和路径压缩的特点,可以算是可持久化的另外一个用途!

(可持久化还可以建线段树省空间,以及查询历史版本)

T3:

本题其实并不难

但是看到博弈论不熟悉就吓到了。。。然后考试也没有留足时间

博弈论的操作是一个树形结构。

决策必须是一些字符串的前缀?trie树就是博弈树!

必然要处理先手必胜还是先手必败,trie的叶子为0,dp一遍即可。

但是为了眼光长远,必须该输就输,所以考虑能否想输就输(这个时候目标刚好相反)

这个时候,对方一定会想让你赢。

所以本质相同,trie的叶子为1,再dp一遍就可以知道一个位置能否输掉了。

然后讨论

1.必胜,可以输掉:先手无敌,可以直接狂输,然后赢即可。

2.必胜,不能想输就输,即先手赢且只能赢。K为奇数,先手必胜,偶数,先手必败

3.必输,先手必输

这样一定考虑了所有情况。

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=1e5+5;
int ch[N][26];
char s[N];
int t;
int cnt;
void ins(char *s,int l){
    int now=1;
    for(reg i=1;i<=l;++i){
        int x=s[i]-'a';
        if(ch[now][x]==0) ch[now][x]=++cnt;
        now=ch[now][x];
    }
}
int dfs1(int x){
    int ret=0;
    for(reg i=0;i<26;++i){
        if(ch[x][i]){
            int tmp=dfs1(ch[x][i]);
            if(tmp==0) ret=1;
        }
    }
    return ret;
}
int dfs2(int x){
    int ret=0;
    int has=0;
    for(reg i=0;i<26;++i){
        if(ch[x][i]) {
            ++has;
            int tmp=dfs2(ch[x][i]);
            if(tmp==0) ret=1;
        }
    }
    if(has) return ret;
    return 1;
}
void clear(){
    cnt=1;
    memset(ch,0,sizeof ch);
}
int main(){
    rd(t);
    while(t--){
        clear();
        int n;
        int k;
        rd(n);rd(k);
        for(reg i=1;i<=n;++i){
            scanf("%s",s+1);
            int len=strlen(s+1);
            ins(s,len);
        }
        int bs=dfs1(1);
        int kb=dfs2(1);
        if(bs){
            if(kb) puts("Pure");
            else{
                if(k&1) puts("Pure");
                else puts("Dirty");
            }
        }else{
            puts("Dirty");
        }
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/28 15:11:22
*/
View Code

还可以简单粗暴一些:
连续K轮,每轮换一个先手,相当于叶子作为下一层的根节点

即:

跑两遍dp之后,

可以倍增处理

也可以发现其实叶子颜色相同,会0/1重复,(本质和第一个方法就一致了)

讨论即可。

根据本题,博弈论的一个总结是:
考虑必胜必败,后继状态,从边界开始处理

还可以考虑可以输,不能输另外两种关系。

原文地址:https://www.cnblogs.com/Miracevin/p/10191886.html