• 【LuoguP4156】论战捆竹竿


    题目链接

    题意简述

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

    (nleq 5*10^5 ,wleq 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
    ",ans);
    	}
    }
    
    
    
  • 相关阅读:
    下定决心
    SPFA
    Linux下一些常用的命令
    如何设计符合RESTful风格的API
    django中的第三方:富文本编辑器和itsdangerous加密
    redis主从
    redis集群
    django中关联(一对多)查询的两种方式,理一理
    关于Django中的迁移文件
    日常工作中Git的正确使用姿势
  • 原文地址:https://www.cnblogs.com/NeosKnight/p/10397313.html
Copyright © 2020-2023  润新知