把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4156】[WC2016] 论战捆竹竿(border性质)

点此看题目

  • 给定一个长度为\(n\)的字符串\(s\)
  • 你有一个初始为空的串\(S\),你可以任意次将\(s\)加到\(S\)的末尾,若\(s\)的开头\(i\)个字符和\(S\)的结尾\(i\)个字符相同,可以选择不加入这相同的部分(即仅加入\(s\)\(i+1\sim n\)个字符)。
  • \(S\)\([1,m]\)中可能达到的长度种数。
  • 数据组数\(\le5\)\(n\le5\times10^5,m\le10^{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+k\cdot d(k\in[1,l])\),从每个点\(i\)\((i+d)\%x\)连边,就意味着每个点可以向从它出发\(l\)步以内的点转移:

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

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

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

处理不同的等差数列之间需要切换模数,相当于每次可以加入\(k\cdot 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\n",t);//枚举每种余数统计答案
	}return 0;
}
posted @ 2021-07-11 08:57  TheLostWeak  阅读(135)  评论(0编辑  收藏  举报