zoukankan      html  css  js  c++  java
  • [集训队作业2018] 串串划分

    链接

    毒瘤题。

    首先这么多条件一看就很不好 dp,考虑合并条件 1,2,即我们钦定一个循环串是按照最小循环节分割开的。可以发现这样不符合条件1的串变成了不符合条件2的串。

    然后考虑容斥,即“没有相同”看作 (sum_{k}(-1)^k ext{至少有k个相同的方案数})

    考虑一次转移恰好是原串的一次分割,无论这次分割的是否为循环串则有且仅有一种分割。用 (R(s)) 表示串 (s) 的最小循环节循环次数,即最小正周期,可以列出 (f_i=sum_{j}(-1)^{R(s[j+1:i])-1} f_j)

    这个式子显然是 (O(n^2)) 的(R函数预处理)。

    我们发现对于 (R) 是奇数的串这么算有点浪费,考虑换一种写法:

    [f_i=sum_{j=1}^{i-1} f_j-2 imessum_{j=1}^{i-1}[R(s[i:j]) ext{ is even}]f_j ]

    但是这样仍然是 (O(n^2)) 的。

    为了解决这个问题,我们引入一个定义:本原平方串(primitive square)。

    定义一个串 (s) 是本原平方串,当且仅当其最小正周期恰好为 (frac s 2)

    可以证明,任何字符串的本原平方串的个数是 (O(nlog n)) 的。

    考虑利用这个性质探究循环串 (s[i:j]) 的性质:如果 (R(s[i:j]) ext{ is even}) 必然对应 (s[i:j]) 是由若干相同的本原平方串构成。

    而一个本原平方串 (s[i:j]) 必定对应一个 Runs ((l,r,frac{j-i}2))。所以我们不妨找到所有 Runs ((l,r,p)),显然以任意起点 (iin[l,l+2p-1)) 都有 (s[i:i+2p]) 是一个本原串。

    对于起点 (i) 我们设置 (i+2kp,kinmathbb{Z}) 作为二元组 ((i,p)) 的关键点,那么任意两个关键点之间都是合法的本原串。

    所以对于 (f_i) 我们统计 (i) 位置的关键点信息。同时对于每个二元组我们统计当前所有的 (f_p) 之和。由于本原平方串的性质,这样做是 (O(nlog n)) 的。

    总复杂度 (O(nlog n))

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<unordered_set>
    #define ll long long
    #define N 200010
    #define mod 998244353
    #define B 2333
    using namespace std;
    char str[N];
    int s[N],n;
    int h[N],bs[N];
    int get(int l,int r){return (h[r]-1ll*h[l-1]*bs[r-l+1]%mod+mod)%mod;}
    int lcp(int x,int y)
    {
        int l=1,r=n-max(x,y)+1,res=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(get(x,x+mid-1)==get(y,y+mid-1)) l=mid+1;
            else r=mid-1;
        }
        return r;
    }
    int lcs(int x,int y)
    {
        int l=1,r=min(x,y),res=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(get(x-mid+1,x)==get(y-mid+1,y)) l=mid+1;
            else r=mid-1;
        }
        return r;
    }
    bool cmp(int l1,int l2)//s[l1:]<s[l2:]
    {
        int l=lcp(l1,l2);
        return s[l1+l]<s[l2+l];
    }
    int nxt[N];
    int tt,pr[N*20],f[N],sf[N*20];
    vector<int>g[N*20];
    unordered_set<ll>fd;
    void lyndon(bool ell)
    {
        for(int i=n-1;i;i--)
            for(nxt[i]=i+1;nxt[i] && cmp(nxt[i],i)==ell;nxt[i]=nxt[nxt[i]]);
    }
    void make_runs()
    {
        for(int k=n-1;k;k--)
        if(nxt[k])
        {
            int dl=lcs(k,nxt[k]),dr=lcp(k,nxt[k]),p=nxt[k]-k;
            int l=k-dl+1,r=nxt[k]+dr-1;
            if(dl+dr<=p || !fd.insert(1ll*r*N+l).second) continue;
            for(int i=l-1;i<l+2*p-1 && i+2*p<=r;i++)
            {
                int x=++tt;pr[x]=p;
                for(int j=i+2*p;j<=r;j+=2*p) g[j].push_back(x);
            }
        }
    }
    void init()
    {
        bs[0]=1;
        for(int i=1;i<=n;i++) h[i]=(1ll*h[i-1]*B+s[i])%mod,bs[i]=1ll*bs[i-1]*B%mod;
    }
    int main()
    {
        scanf("%s",str+1);
        n=strlen(str+1);
        for(int i=1;i<=n;i++) s[i]=str[i]-'a'+1;
        init();
        lyndon(0);make_runs();
        lyndon(1);make_runs();
        int sm=f[0]=1;
        for(int i=1;i<=n;i++)
        {
            f[i]=sm;
            for(int v:g[i]) f[i]=(f[i]-2ll*(sf[v]=(sf[v]+f[i-2*pr[v]])%mod)%mod+mod)%mod;
            sm=(sm+f[i])%mod;
        }
        printf("%d
    ",f[n]);
        return 0;
    }
    
  • 相关阅读:
    算法-经典趣题-寻找假银币
    一天一个 Linux 命令(3):cat 命令
    算法-经典趣题-青蛙过河
    常用数据库有哪些?
    SpringBoot2.0入门教程(一) 快速入门,项目构建HelloWorld示例
    一天一个 Linux 命令(2):ls 命令
    算法-经典趣题-爱因斯坦阶梯问题
    一天一个 Linux 命令(1):vim 命令
    什么是开发环境、测试环境、UAT环境、仿真环境、生产环境?
    算法-经典趣题-渔夫捕鱼
  • 原文地址:https://www.cnblogs.com/Flying2018/p/14032526.html
Copyright © 2011-2022 走看看