2019HDU多校赛 第十场 HDU 6694 Play Games with Rounddog(后缀自动机 + 线性基)

 

 

大致题意:给你一个字符串S,然后q个询问,每次给出S的一个子串T。对于每个询问的子串T,Calabash可以在S中选择任意个以T作为后缀的子串,然后生成子串对应数目个石子堆,每堆的石子数量等于w[对应子串在S中出现的次数]。然后Rounddog可以从这么多堆石子中选择任意堆的石子(至少选一堆),两人开始玩Nim游戏,Calabash先手。现在问Calabash是否存在必胜策略,如果有输出Calabash在必胜策略下,能够选出来的最多的石子数目。

比赛的时候大概想到了,猜了一个结论,但是没敢写……

题目有点绕,但是经过分析我们可以慢慢理解题意。首先,直接考虑两个人玩Nim游戏,Calabash想要先手必胜,那么在游戏开始的时候所有堆的数字数目异或起来一定不能为0。这个条件其实很容易转换,因为异或和不为0,那么可以考虑线性基。显然如果要必胜,那么把最后的石子堆数插入线性基,一定都是可以成功插入的。所以,Calabash一定是可以找到至少一种方案使得他必胜。

然后考虑石子数目最多,也就是找一个和最大的线性基。这里就要用我们猜的到结论,就是对于一堆数字,从大的数字往小的数字依次往线性基里面插,这样的线性基基底的和一定是最大的。这个结论我也不太会证明,但是感觉确实是这个样子的。不过我比赛的时候也没敢写,赛后看别人有这么写过了的才来补。

这样,我们就完成了后半部分,现在我们考虑前半部分。这里需要知道,所有以某个子串作为后缀的串在原串中出现的次数,我们考虑用后缀自动机。考虑后缀自动机的parent树,根据定义,某个节点在parent树中所有的后代对应的子串的后缀都包含这个节点对应的串,所有它的后代节点的数量即right就是本题所求的出现次数。对于一个询问给定的子串,我们可以找到它在后缀自动机中的位置,这个位置以及其后代所有节点都是包含子串的可选串,对应他们的w[right]即为线性基中可以插入的数字。

我们可以预处理每个位置的最终答案,因为对于某个点,它的答案就是尝试把它所有的后代对应的right从大到小往一个线性基里面插,这样最后的总和。直接排序去做当然是不行的,我们考虑每个点的贡献,每个点的right可以插的线性基就是从它自己开始一直往上到根的路径上所有的线性基。我们考虑首先对所有的点按照right从大到小排序,从right大的点开始往上插入。当插入到一个祖先发现不能插入时,就不需要继续尝试往上插,因为再在当前祖先不能插入,再往上也一定不能插入。由于本题w的范围是2的58次方以内,所以每个线性基最多插入的数字就是58个,因此这样的复杂度就是O(NlogW)。

另外需要注意由于58*2^58约等于2^63,因此可能需要用unsigned long long。具体见代码:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define eps 1e-4
#define pi 3.141592653589793
#define P 1000000007
#define LL long long
#define ULL unsigned LL
#define LDB long double
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<"      :   "<<x<<endl
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

const int N = 200010;

int n,f[N],num[N],dep[N],dp[19][N],Right[N],tot;
vector<int>g[N]; LL w[N],p[N];
char s[N];

struct Linear_Basis
{
    LL b[58]; ULL ans;
    inline void init(){ans=0;memset(b,0,sizeof(b));}

    inline bool ins(LL x)
    {
        LL tmp=x;
        for(int i=57;i>=0;i--)
            if (x&(1LL<<i))
            {
                if (!b[i]) {b[i]=x;ans+=tmp;break;}
                x^=b[i];
            }
        return x>0;
    }

} LB[N];

struct Suffix_Automation
{
    int tot,cur;
    struct node{int ch[26],len,fa;} T[N];
    inline void init()
    {
        cur=tot=1;memset(T,0,sizeof(T));
        memset(Right,0,sizeof(Right));
    }

    void ins(int x,int id)
    {
        int p=cur;cur=++tot;T[cur].len=id;Right[cur]++;
        for(;p&&!T[p].ch[x];p=T[p].fa) T[p].ch[x]=cur;
        if (!p) {T[cur].fa=1;return;}int q=T[p].ch[x];
        if (T[p].len+1==T[q].len) {T[cur].fa=q;return;}
        int np=++tot; memcpy(T[np].ch,T[q].ch,sizeof(T[q].ch));
        T[np].fa=T[q].fa; T[q].fa=T[cur].fa=np; T[np].len=T[p].len+1;
        for(;p&&T[p].ch[x]==q;p=T[p].fa) T[p].ch[x]=np;
    }

    inline void build()
    {
        for(int i=2;i<=tot;i++) g[T[i].fa].pb(i);
    }

} SAM;

void ST()
{
    for (int j=1;j<19;j++)
        for (int i=1;i<=tot;i++)
            dp[j][i]=dp[j-1][dp[j-1][i]];
}

void dfs(int x,int fa,int depth)
{
    dp[0][x]=fa;
    dep[x]=depth;
    for (auto i:g[x])
    {
        if (i==fa) continue;
        dfs(i,x,depth+1);
        Right[x]+=Right[i];
    }
}

namespace IO{
    #define BUF_SIZE 100000
    #define OUT_SIZE 100000
    #define ll long long
    //fread->read

    bool IOerror=0;
    inline char nc(){
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
        if (p1==pend){
            p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin);
            if (pend==p1){IOerror=1;return -1;}
        }
        return *p1++;
    }
    inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';}
    inline void read(int &x){
        bool sign=0; char ch=nc(); x=0;
        for (;blank(ch);ch=nc());
        if (IOerror)return;
        if (ch=='-')sign=1,ch=nc();
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
        if (sign)x=-x;
    }
    inline void read(ll &x){
        bool sign=0; char ch=nc(); x=0;
        for (;blank(ch);ch=nc());
        if (IOerror)return;
        if (ch=='-')sign=1,ch=nc();
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
        if (sign)x=-x;
    }
    inline void read(char *s){
        char ch=nc();
        for (;blank(ch);ch=nc());
        if (IOerror)return;
        for (;!blank(ch)&&!IOerror;ch=nc())*s++=ch;
        *s=0;
    }
}

struct Ostream_fwrite{
        char *buf,*p1,*pend;
        Ostream_fwrite(){buf=new char[BUF_SIZE];p1=buf;pend=buf+BUF_SIZE;}
        void out(char ch){
            if (p1==pend){
                fwrite(buf,1,BUF_SIZE,stdout);p1=buf;
            }
            *p1++=ch;
        }
        void print(ULL x){
            static char s[20],*s1;s1=s;
            if (!x)*s1++='0';if (x<0)out('-'),x=-x;
            while(x)*s1++=x%10+'0',x/=10;
            while(s1--!=s)out(*s1);
        }
        void flush(){if (p1!=buf){fwrite(buf,1,p1-buf,stdout);p1=buf;}}
        void print(char *s){while (*s)out(*s++);}
        ~Ostream_fwrite(){flush();}
    }Ostream;
inline void print(ULL x){Ostream.print(x);}
inline void print(char *s){Ostream.print(s);}
inline void flush(){Ostream.flush();}
inline bool cmp(int a,int b){return p[a]>p[b];}
using namespace IO;


int main()
{
    int T; read(T);
    while(T--)
    {
        SAM.init();
        memset(g,0,sizeof(g));
        read(n); read(s);
        for(int i=0;i<n;i++)
        {
            SAM.ins(s[i]-'a',i+1);
            num[i+1]=SAM.cur;
        }
        tot=SAM.tot;
        for(int i=1;i<=n;i++) read(w[i]);
        SAM.build(); dfs(1,0,0); ST();
        for(int i=1;i<=tot;i++)
            LB[i].init(),f[i]=i,p[i]=w[Right[i]];
        sort(f+1,f+1+tot,cmp);
        for(int i=1;i<=tot;i++)
            for(int now=f[i];now;now=dp[0][now])
                if (!LB[now].ins(p[f[i]])) break;
        int q; read(q);
        while(q--)
        {
            int x,y;
            read(x); read(y);
            int len=y-x+1;y=num[y];
            for(int i=18;i>=0;i--)
                if (SAM.T[dp[i][y]].len>=len) y=dp[i][y];
            print(LB[y].ans); print("\n");
        }
        flush();
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值