【LuoguP4156】论战捆竹竿

题目链接

题意简述

你有一个长度为 n 的字符串 , 将它复制任意次 , 复制出的串的前缀可以与之前的串的后缀重叠在一起 , 问最后总共可能的长度数目 , 长度不能超过 \(w\)
多组数据。

\(n\leq 5*10^5 ,w\leq 10^{18}\)

Sol

显然每次可以重叠的部分是原串的一个 boder
假设这个boder长度为 \(L\) , 那么长度可以只增加 \(n-L\)
那么就是一个存在性完全背包问题。
那么先求出所有boder , 之后显然可以用同余类最短路来求答案。
不过这个样子复杂度太高了。
最短路的做法是有所多余的 , 每一个点只会向后连边 , 连边的模式也是一定的 , 最短路和转移之间的顺序就是没有关系的 , 我们可以分开考虑每一种连边方式 , 假设这个长度为 \(L\)
由数学知识我们可以知道他将原来的长度为 \(n\) 的数组分成了 \(gcd(n,L)\) 个在这次转移中互不影响的环 , 我们在每一个环里找到 距离最小的那个点开始转移就可以了。
这样复杂度就是 \(O(T*n^2)\) , 只有 30'

接下来就要用到一个很强的性质了 , 一个字符串的 \(boder\) 形成的等差数列的个数不会超过 \(log\)
证明不会。

这又什么用呢?
考虑一个等差数列在转移的时候有什么优秀的性质。
我们假设首项为\(x\),公差为 \(d\) ,长度为 \(l\)
那么假设我们在模 \(x\) 的意义下进行这些转移 , 这些长度都可以等价于一个长度为 \(d\) 的转移方式!这样我们要考虑的转移方式总数就下降到了 \(log\) 级别。由于长度只有 \(l\) ,我们转移的时候最多就只能从前 \(l-1\) 个转移过来 , 用一个单调队列来维护转移即可。
但是这样还有一个问题 , 因为这样同余类最短路的模数不就一直在变?
其实是可以转换的 , 具体方式为:
假设新的模数 \(p\) 下的dp数组为 \(g\) , 原来的模数为 \(q\) ,数组为 \(f\) ,那么首先可以 \(g[f[i]\%p]=f[i]\) , 这样有什么问题呢?
容易发现 , 由于之前我们相当于是没有进行 长度为 \(q\) 的转移的 , 所以这里的 dp 数组会有些值是不对的 , 这样的话我们重新进行一次长度为 \(q\) 的转移就好了。

code:

#include<bits/stdc++.h>
using namespace std;
#define next NEXTT
#define Set(a,b) memset(a,b,sizeof(a))
#define Copy(a,b) memcpy(a,b,sizeof(a))
const int N=5e5+10;
typedef long long ll;
char S[N];
int next[N];
int T;
ll n,w,ans,f[N],g[N];
int len[N],tot=0,Idex,vis[N];
int head,tail;
int que[N],V[N];
inline void Update(int pL,int nL,int d,int cnt){
    Set(g,-1);
    for(int i=0;i<pL;++i) if(~f[i]) {int res=f[i]%nL;if(~g[res]) g[res]=min(g[res],f[i]);else g[res]=f[i];}
    Copy(f,g);
    ++Idex;
    for(int i=0;i<nL;++i) {
        if(vis[i]==Idex) continue;
        int mip=i;vis[i]=Idex;
        for(int j=(i+pL)%nL;j^i;j=(j+pL)%nL) {
            if((!~f[mip])||((~f[j])&&f[mip]>f[j])) mip=j;
            vis[j]=Idex;
        }
        if(~f[mip]){
            for(int pre=mip,j=(mip+pL)%nL;j^mip;pre=j,j=(j+pL)%nL){if(!~f[j]) f[j]=f[pre]+pL;else f[j]=min(f[j],f[pre]+pL);}
        }
    }++Idex;
    for(int i=0;i<nL;++i) {
        if(vis[i]==Idex) continue;
        int mip=i;vis[i]=Idex;
        for(int j=(i+d)%nL;j^i;j=(j+d)%nL){
            if((!~f[mip])||((~f[j])&&f[j]<f[mip])) mip=j;
            vis[j]=Idex;
        }
        if(!~f[mip]) continue;
        head=1,tail=1;que[1]=1;V[1]=mip;
        for(int k=2,j=(mip+d)%nL;j^mip;j=(j+d)%nL,++k){
            while(head<=tail&&k-que[head]>=cnt) ++head;
            if(head<=tail) {if(~f[j]) f[j]=min(f[j],f[V[head]]+(k-que[head])*d+nL);else f[j]=f[V[head]]+(ll)(k-que[head])*d+nL;}
            if(~f[j]){
                while(head<=tail&&f[j]<=(ll)d*(k-que[tail])+f[V[tail]]) --tail;
                que[++tail]=k,V[tail]=j;
            }
        }
    }
    return;
}
void Solve(){
    int lst=n;--tot;
    reverse(len+1,len+1+tot);
    int L;
    for(int i=1,j;i<=tot;i=j) {
        j=i+1;int d=len[i]-len[j];
        if(j>tot) d=0;
        while(j<=tot&&len[j-1]-len[j]==d) ++j;
        L=len[j-1];Update(lst,L,d,j-i);lst=L;
    }
    ans=0;
    for(int j=0;j<lst;++j) if(~f[j]&&f[j]<=w) ans+=(w-f[j])/lst+1;
    return;
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("jie.in","r",stdin);
    freopen("jie.out","w",stdout);
#endif
    scanf("%d",&T);
    while(T--){
        Set(vis,0);Idex=0;
        scanf("%lld %lld",&n,&w);
        ans=0;w-=n;scanf("%s",S+1);
        register int i,j=0;Set(next,0);
        for(i=2;i<=n;++i) {
            while(j&&S[i]!=S[j+1]) j=next[j];
            if(S[i]==S[j+1]) ++j;next[i]=j;
        }j=n;tot=0;
        while(j){len[++tot]=n-next[j];j=next[j];}
        Set(f,-1),f[0]=0,Solve();
        printf("%lld\n",ans);
    }
}

猜你喜欢

转载自www.cnblogs.com/NeosKnight/p/10397313.html