zoukankan      html  css  js  c++  java
  • 【洛谷4156】[WC2016] 论战捆竹竿(border性质)

    点此看题目

    • 给定一个长度为(n)的字符串(s)
    • 你有一个初始为空的串(S),你可以任意次将(s)加到(S)的末尾,若(s)的开头(i)个字符和(S)的结尾(i)个字符相同,可以选择不加入这相同的部分(即仅加入(s)(i+1sim n)个字符)。
    • (S)([1,m])中可能达到的长度种数。
    • 数据组数(le5)(nle5 imes10^5,mle10^{18})

    (border)性质

    显然,(S)的末尾(n)个字符始终是(s)

    因此,除了第一步必须加入整个(s)外,无论何时,我们能够加入一段长为(x)的字符串,当且仅当(n-x)(s)的一个(border)

    于是先用(KMP)求出(s)的所有(border),那么就求出了单次可能加入的所有长度。

    考虑到(border)的性质,所有的(border)可以划分为至多(O(logn))个等差数列。

    则单次可能加入的所有长度也可以划分为至多(O(logn))个等差数列,不妨设第(i)个等差数列首项为(x_i),末项为(y_i),公差为(d_i),并记(l_i=frac{y_i-x_i}{d_i})

    同余最短路

    依次考虑每一个等差数列(x,d,l)(定义见前面)。

    这种问题其实有一个套路,就是同余最短路

    我们求出(f_i)表示可能达到的模(x)(i)的最小长度,由于(x)是一个可加入长度,则(f_i+x,f_i+2x,..)都是可能达到的。

    而我们每次可以加入(x+kcdot d(kin[1,l])),从每个点(i)((i+d)\%x)连边,就意味着每个点可以向从它出发(l)步以内的点转移:

    [f_j=min{f_i+x+(pos_j-pos_i) imes d} ]

    根据(f_i-pos_i imes d)开一个单调队列优化转移即可。

    具体实现中注意这张图实际上可以被分成(gcd(x,d))个环,对于每个环我们从环上最小的(f_i)断环为链开始转移(因为最小的(f_i)肯定无法被更新得更优)。

    处理不同的等差数列之间需要切换模数,相当于每次可以加入(kcdot lst),从每个点(i)((i+lst)\%x)连边,由于(k)没有了范围限制且转移时没有了(x)这个常数项,同样在最小的(f_i)断环为链,此时每次只要从上一个点转移即可,算是前面过程的简化版。

    代码:(O(Tnlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Rg register
    #define RI Rg int
    #define Cn const
    #define CI Cn int&
    #define I inline
    #define W while
    #define N 500000
    #define LL long long
    #define INF (LL)1e18
    using namespace std;
    int n;LL m;char s[N+5];I int gcd(CI x,CI y) {return y?gcd(y,x%y):x;}
    int lst,q[N+5],c[N+5];LL f[N+5],g[N+5];I void Solve(CI x,CI y,CI d)//处理一个等差数列
    {
    	RI i,j,r,s;for(i=0;i^lst;++i) g[i]=f[i],f[i]=INF;//记录原本的答案
    	for(i=0;i^lst;++i) f[g[i]%x]=min(f[g[i]%x],g[i]);//初始化新的答案
    	for(r=gcd(lst,x)-1;~r;--r)//切换模数,枚举每一个环
    	{
    		for(i=(r+lst)%x,s=r;i^r;i=(i+lst)%x) f[i]<f[s]&&(s=i);//找到最小f[i]所在位置
    		for(i=(s+lst)%x,j=s;i^s;j=i,i=(i+lst)%x) f[i]=min(f[i],f[j]+lst);//断环为链,每次从上一个点转移
    	}
    	if(lst=x,!d) return;RI l=(y-x)/d,H,T,ct;LL t;for(r=gcd(x,d)-1;~r;--r)//更新答案,枚举每一个环
    	{
    		for(i=(r+d)%x,s=r;i^r;i=(i+d)%x) f[i]<f[s]&&(s=i);//找到最小f[i]所在位置
    		for(i=(s+d)%x,q[H=T=1]=s,c[1]=ct=1;i^s;i=(i+d)%x)//断环为链
    		{
    			++ct;W(H<=T&&ct-c[H]>l) ++H;f[i]=min(f[i],f[q[H]]+x+(ct-c[H])*d);//从l步以内的点转移
    			W(H<=T&&f[q[T]]-1LL*c[T]*d>=f[i]-1LL*ct*d) --T;q[++T]=i,c[T]=ct;//维护单调队列
    		}
    	}
    }
    int nxt[N+5];I void KMP()//KMP求border
    {
    	RI i,j;for(i=2,nxt[j=0]=-1;i<=n;s[j+1]==s[i]&&++j,nxt[i++]=j) W(j&&s[j+1]^s[i]) j=nxt[j];//求next数组
    	for(lst=n,i=nxt[n];~i;i=nxt[i]) if(!~nxt[i]) Solve(n-i,n-i,0);else//只剩一项
    		{j=i;W(~nxt[nxt[i]]&&i-nxt[i]==nxt[i]-nxt[nxt[i]]) i=nxt[i];Solve(n-j,n-(i=nxt[i]),j-nxt[j]);}//抠出一个等差数列
    }
    int main()
    {
    	RI Tt,i;LL t=0;scanf("%d",&Tt);W(Tt--)
    	{
    		for(scanf("%d%lld%s",&n,&m,s+1),i=1;i<=n;++i) f[i]=INF;
    		for(KMP(),t=i=0;i^lst;++i) t+=m-n>=f[i]?(m-n-f[i])/lst+1:0;printf("%lld
    ",t);//枚举每种余数统计答案
    	}return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    学习C++的第一天
    2016.12.28
    无主之地
    一种排序
    街区最短问题
    配对类问题
    蛇形填数
    c#unity
    贪心
    台阶
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4156.html
Copyright © 2011-2022 走看看