洛谷 5112 FZOUTSY 题解

博客观赏效果更佳:
github
cnblogs

题意简述

给定字符串 S S S,设 s u f ( i ) suf(i) suf(i) 表示从 i i i 开始的后缀。支持 q q q 个询问,每次给定 [ l , r ] [l,r] [l,r],求这段区间中有多少对 i , j i,j i,j 使得 s u f ( i ) suf(i) suf(i) s u f ( j ) suf(j) suf(j) 的最长公共前缀长度 ≥ k \ge k k k k k 是一个定值,每次都一样。

(备注: ( i , j ) (i,j) (i,j) ( j , i ) (j,i) (j,i) 是同样的一对,只算一次)

k ≤ ∣ S ∣ ≤ 3 × 1 0 6 , m ≤ 1 0 5 k\le |S|\le 3\times 10^6,m\le 10^5 kS3×106,m105,并且满足 n 2 m ≤ 1 0 15 n^2m\le 10^{15} n2m1015

思路

n 2 m ≤ 1 0 15 n^2m\le 10^{15} n2m1015?这看起来很奇怪。

冷静分析一下,这提示着我们, O ( n m ) O(n\sqrt{m}) O(nm ) 的算法是可以通过的。仔细想一下 ,带根号的静态区间询问的算法…

莫队

然后, s u f ( i ) , s u f ( j ) suf(i),suf(j) suf(i),suf(j) 最长公共前缀长度 ≥ k \ge k k,等价于: i i i 往后 k k k 个的子串,和 j j j 往后 k k k 个的子串相同。

然后我们要快速比较两端子串是否相同…

哈希

于是问题变为:

  1. 处理第 i i i 位往后 k k k 个位置的哈希值,设为 h [ i ] h[i] h[i]
  2. 每次询问,就相当于询问 h [ l , r ] h[l,r] h[l,r] 中有多少不重复的数,显然可以莫队维护。

代码

#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
namespace Flandre_Scarlet
{
	#define N 3000006
	#define ll long long
	#define F(i,l,r) for(int i=l;i<=r;++i)
	#define D(i,r,l) for(int i=r;i>=l;--i)
	#define Fs(i,l,r,c) for(int i=l;i<=r;c)
	#define Ds(i,r,l,c) for(int i=r;i>=l;c)
	#define MEM(x,a) memset(x,a,sizeof(x))
	#define FK(x) MEM(x,0)
	#define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
	#define p_b push_back
	#define sz(a) ((int)a.size())
	#define iter(a,p) (a.begin()+p)
	int I()
	{
	    int x=0;char c=getchar();int f=1;
	    while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
	    while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	    return (x=(f==1)?x:-x);
	}
	void Rd(int cnt,...)
	{
	    va_list args; va_start(args,cnt);
	    F(i,1,cnt) {int* x=va_arg(args,int*);(*x)=I();}
	    va_end(args);
	}

	int n,m,k; char a[N];
	void Input()
	{
		Rd(3,&n,&m,&k);
		scanf("%s",a+1);
	}

	ll pre[N],pw[N];
	ll RHash(int l,int r) {return pre[r]-pre[l-1]*pw[r-l+1];} // Range Hash,求区间哈希值
	
	ll h[N]; unordered_map<ll,int> desc;
	
	struct node{int l,r,id;}q[N]; int sn; bool operator<(node a,node b){return (a.l/sn<b.l/sn) or (a.l/sn==b.l/sn and a.r<b.r);} 
    // 莫队
    // sn 为块大小
	ll cnt[N]; ll cur=0;
	void Add(int x){cur+=cnt[h[x]]; cnt[h[x]]++;} // 加入一个位置
	void Del(int x){cnt[h[x]]--; cur-=cnt[h[x]];} // 删除一个位置
	ll ans[N];

	void Soviet()
	{
		pw[0]=1; F(i,1,n) pw[i]=pw[i-1]*233ll; // 哈希底数开大点
		F(i,1,n) pre[i]=(pre[i-1]*233ll+(a[i]-'a'));

		F(i,1,n-k+1) h[i]=RHash(i,i+k-1); // 预处理出 h[i] 数组
		int dcnt=0;
		F(i,1,n-k+1) 
		{
			if (!desc[h[i]]) desc[h[i]]=++dcnt;
			h[i]=desc[h[i]];
		}
        // 离散化
        // 只是一个重新编号
        // 我们只保证:原来相等的还相等,原来不相等的还不相等
        // 并不保证 < 的关系 (也没有必要)

		sn=n/sqrt(m); 
        // 块长开 n/sqrt(m)
		// 据说开 sqrt(n) 也不会被卡
        F(i,1,m) q[i]=(node){I(),min(I(),n-k+1),i};
        // 因为右端点 >n-k+1 的时候显然不可能有长度为 k 的最长公共前缀
        // 所以 r 和 n-k+1 取 min
		sort(q+1,q+m+1);
		int l=1,r=0; cur=0ll;
		F(i,1,m)
		{
			if (q[i].l>q[i].r) {ans[q[i].id]=0; continue;} // 会有这种情况...判掉
			while(r<q[i].r) ++r,Add(r);
			while(r>q[i].r) Del(r),--r;
 			while(l<q[i].l) Del(l),++l;
			while(l>q[i].l) --l,Add(l);
			ans[q[i].id]=cur;
		} 
		F(i,1,m) printf("%lld\n",ans[i]);
	}

	#define Flan void
	Flan IsMyWife()
	{
		Input();
		Soviet();
	}
	#undef int //long long 
}
int main()
{
	Flandre_Scarlet::IsMyWife();
	getchar();getchar();
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值