首页 > 其他分享 >CF204E Little Elephant and Strings 题解

CF204E Little Elephant and Strings 题解

时间:2023-02-24 13:56:10浏览次数:32  
标签:Little int 题解 mid 后缀 区间 -- CF204E sa

由于是多个串,还与每个子串的信息有关,很容易想到用 SA 或广义 SAM。这里选择用 SA。

首先先把字符串转化为数组,连接起来,中间用一些不会出现的数。处理出后缀数组与 \(height\) 数组,下面简写为 \(h\)。

所以我们转化后实际上就是求一个后缀中有多长的前缀在 \(k\) 个来源不同串的后缀的前缀出现过,答案贡献即为前缀的长度。

对于一个区间 \((l,r)\) 包含了 \(k\) 个不同的串中的后缀,那么这些串有公共的长度为 \(\min(h_i)\hspace{0.1cm}(l<i\le r)\) 的公共前缀。

可以证明,如果有两个区间 \((l_1,r_1)\),\((l_2,r_2)\),且 \(l_1\ge l_2\),\(r1 \le r2\),那么 \(\min(h_i)>\min(h_j)\hspace{0.1cm}(l_1<i\le r_1,l_2<j\le r_2)\),因为具有单调性。

所以只有刚好包含 \(k\) 个来源不同串中的后缀时,才会对这个后缀贡献答案。

那么我们就可以用单调队列维护这个区间,当这个区间达到 \(k\) 个来源不同串的后缀时,我们将左端点往右推,并且更新答案,区间最小值用单调队列维护。然而如果多个区间包含一个点,那么求的应该是最大值,因为长度大的符合条件,那么小的也符合条件,那求最大值有很多求法,但线段树一定是最无脑的,所以直接选择线段树。

一个易错点,对于相同的右端点,不能只用最小的包含 \(k\) 个来源不同串的后缀的区间更新,因为这样会让这个区间左边的点无法更新到答案,在缩短区间前且满足为 \(k\) 个时,可能对于他们这个区间最小则是这个,而如果回退了就不能照顾这些点对应的最小区间(简单来说就是有些值更新不到),所以要边回退边更新。

时间复杂度:\(O(n\times \log(n))\)。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#define int long long
#define pii pair<int,int> 
using namespace std;
const int N=2e5+5;
int T,kt,a[N],b[N],rk[N],sa[N],cnt[N],n,p[N],h[N],vis[N],laz[4*N],f[4*N],sp[N],c[N];
long long ans[N];
char s[N];
deque<pii>q;
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
inline void pushup(int x)
{
	f[x]=max(f[ls(x)],f[rs(x)]);
}
inline void fix(int x,int l,int r,int k)
{
	f[x]=max(f[x],k);
	laz[x]=max(laz[x],k);
}
inline void pushdown(int x,int l,int r)
{
	int mid=(l+r)>>1;
	fix(ls(x),l,mid,laz[x]);
	fix(rs(x),mid+1,r,laz[x]);
	laz[x]=0;
}
void update(int x,int l,int r,int nl,int nr,int k)
{
	if(l>=nl&&r<=nr)
	{
		f[x]=max(f[x],k);
		laz[x]=max(laz[x],k);
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid,nl,nr,k);
	if(mid<nr)update(rs(x),mid+1,r,nl,nr,k);
	pushup(x);
}
void getans(int x,int l,int r)
{
	if(l==r)
	{
		//cout<<l<<" "<<sa[l]<<" "<<p[sa[l]]<<" "<<f[x]<<endl;
		ans[p[sa[l]]]+=f[x];
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	getans(ls(x),l,mid);
	getans(rs(x),mid+1,r);
}
signed main()
{
	scanf("%lld%lld",&T,&kt);
	int m=26;
	for(int i=1;i<=T;i++)
	{
		scanf("%s",s+1);
		int len=strlen(s+1);
		for(int j=1;j<=len;j++)a[++n]=s[j]-'a',p[n]=i,sp[n]=a[n];
		for(int j=n-len+1;j<=n;j++)c[j]=n-j+1;
		a[++n]=i+26,sp[n]=a[n],m=i+26;
		if(kt==1)printf("%lld ",len*(len+1)/2);
	}
	if(kt==1)return 0;
	sp[n+1]=2e9;
	for(int i=1;i<=n;i++)cnt[a[i]]++;
	for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)sa[cnt[a[i]]--]=i;
	for(int k=1;k<=n;k<<=1)
	{
		int num=0;
		for(int i=n-k+1;i<=n;i++)b[++num]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)b[++num]=sa[i]-k;
		for(int i=0;i<=m;i++)cnt[i]=0;
		for(int i=1;i<=n;i++)cnt[a[i]]++;
		for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i>0;i--)sa[cnt[a[b[i]]]--]=b[i];
		swap(a,b);
		a[sa[1]]=1;
		num=1;
		for(int i=2;i<=n;i++)
		{
			if(b[sa[i]]==b[sa[i-1]]&&b[sa[i]+k]==b[sa[i-1]+k])a[sa[i]]=num;
			else a[sa[i]]=++num;
		}
		if(num==n)break;
		m=num;
	}
	int r=0;
	//for(int i=1;i<=n;i++)cout<<sa[i]<<" ";
	//cout<<endl;
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	for(int i=1;i<=n;i++)
	{
		if(rk[i]==1)continue;
		if(r)r--;
		int j=sa[rk[i]-1];
		while(j+r<=n&&i+r<=n&&sp[i+r]==sp[j+r])++r;
		h[rk[i]]=r;
		//cout<<i<<" "<<r<<endl;
	}
	int sum=0,l=1;
	for(int i=1;i<=n;i++)
	{
		if(!vis[p[sa[i]]])sum++;
		vis[p[sa[i]]]++;
		while(!q.empty()&&q.back().second>=h[i])q.pop_back();
		q.push_back({sa[i],h[i]});
		while(sum>=kt)
		{
			if(!q.empty()&&sa[l]==q.front().first)q.pop_front();
			update(1,1,n,l,i,q.front().second);
			if(sum==kt&&vis[p[sa[l]]]==1)break;
			vis[p[sa[l]]]--;
			if(!vis[p[sa[l]]])sum--;
			l++;
			
		}//cout<<l<<" "<<i<<" "<<sum<<" "<<c[sa[i]]<<" "<<q.front().second<<endl;
		
	}
	getans(1,1,n);
	for(int i=1;i<=T;i++)printf("%I64d ",ans[i]); 
	return 0;
}
/*
2 2
aaab
aaababa
*/

标签:Little,int,题解,mid,后缀,区间,--,CF204E,sa
From: https://www.cnblogs.com/gmtfff/p/cf204e.html

相关文章

  • P5842 [SCOI2012]Blinker 的仰慕者 题解
    题意简述:求\([l,r]\)内各个数位积为\(k\)的数的和。解:在看题解前,请先确认自己是否精通了数位dp模板题,否则会很难理解。对于朴素的数位dp,我们可以记录\(f_{i,j......
  • P5416 [CTSC2016]时空旅行 题解
    首先题中的\(y,z\)好像没啥用……首先对于每一次询问,要求\(\min((x_0-x_i)^2+c_i)\)设\((x_0-x_i)^2+c_i=ans\)。那么\(x_0^2+x_i^2-2x_0x_i+c_i=ans\)所以\(x_0......
  • P7862 [COCI2015-2016#2] DRŽAVA 题解
    适当进行骗分是真的有用。\(40pts\):对于每两个点建立一条边,然后在贪心每次求最小边,在期间进行01背包即可,01背包用于处理模数。设\(dp_{i,j}\)代表以\(i\)为编号的一......
  • P1379 八数码难题 题解
    IDA*练习题由于题目问最小步数,很好想到可以用迭代式加深搜索,或是广搜,这里用的是深搜。枚举每次搜索的深度,也就是移动的步数,然后正常深搜,若达到目标解,返回\(\text{ture}......
  • P3213 [HNOI2011]勾股定理 题解
    据说是NP问题。很明显我们要先预处理出来勾股数对。但由于数过于大,所以常规的枚举是解决不了问题的。但也貌似没有什么很好的办法可以立马找到一个数的勾股数对。所以......
  • P3989 [SHOI2013]阶乘字符串 题解
    由于一些不可抗拒的原因,\(n\ge22\)无解。那么只用考虑\(n\le21\)的情况即可。由于\(n\)的范围缩小,导致状压又可以重新使用,所以考虑状压。设\(f_i\)为\(i\)中......
  • AT655 玉座の間 题解
    首先我们要学习一下费用流。费用流是什么呢,可以理解为边带权值的网络流。那么最小费用最大流,是指在满足最大流的情况下的最小费用。那么我们就要实现这个过程。首先对......
  • P3997 [SHOI2013]扇形面积并 题解
    理解题意后可以把题目看成一个覆盖线段的问题。对于点在\(-m\)上,看成在\(m\)上。对于\(l<r\),不用处理。对于\(l>r\),将问题看成\((l,m)\)和\((-m+1.r)\)两个区......
  • P5616 [MtOI2019]恶魔之树 题解
    期望就是来搞笑的。由于有最小公倍数,所以可以想到分解质因数,对于多个数求最小公倍数,取每个质因子的最大指数,最后相乘即可。既然都知道了这个,那么就想到先统计每个数的个......
  • CF10E Greedy Change 题解
    一个非常离谱的题。首先有结论,如果有\(w\)使贪心不为最优解,那么比\(w\)小的第一个\(a_i\),用贪心法求面值为\(a_i-1\),除了最后选的一个数\(a_j\)会比原方法多选一......