【洛谷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;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4156.html