[BZOJ2384/Ceoi2011]Match

本文详细介绍了如何使用平衡树和线段树来解决字符串匹配问题,通过维护序列的hash值并利用扫描线和平衡树进行高效更新。在处理过程中,还涉及到了树状数组和懒标记传播等数据结构与算法技巧,最终实现O(nlogn)的时间复杂度解决方案。但由于空间限制,采用了动态开点的线段树优化,使得算法在实际运行中更快。
摘要由CSDN通过智能技术生成

Match

题解

字符串题,一看就是hash嘛,不会有人去想kmp吧

我们可以考虑表示出序列 B B B,其中 b i b_{i} bi表示第 i i i小的数再子串中的位置。
怎么表示一个序列呢, h a s h hash hash就行了呗。
所以我们要找到排列 a a a h a s h hash hash值与序列 b b b中每个位置开始的子串的 h a s h hash hash值相等即可。

如何维护序列 b b b的子串的 h a s h hash hash值呢,我们可以考虑扫描线加平衡树来进行维护。
平衡树就维护我们的 B B B序列。
每次从 i i i i + 1 i+1 i+1维护我们会需要将 i i i B B B中删除再将 i + n i+n i+n加入。
删除点 i i i直接去掉值为 i i i所在的节点,也就是值为 1 1 1的节点,再将 B B B全局减一。
而插入点 i + n i+n i+n,我们只需要知道 [ i , i + n ) [i,i+n) [i,i+n)中有多少个数比 b i + n b_{i+n} bi+n小,将平衡树从那个地方裂开加入 b n b_{n} bn即可。
比它小的值的个数我们可以靠v了采用树状数组进行维护。
由于我们维护的是 h a s h hash hash值,我们同样需要将我们的序列 B B B转化成一个值,我们设我们转化的值 x = ∑ i = 1 n ( n + 1 ) i − 1 B i x=\sum_{i=1}^{n}(n+1)^{i-1}B_{i} x=i=1n(n+1)i1Bi
懒标记下传的减一我们明显只需要减去 ∑ i = 0 s i z − 1 ( n + 1 ) i \sum_{i=0}^{siz-1}(n+1)^i i=0siz1(n+1)i,这是可以预处理出来的,而单个点的插入也只会改变一条链,我们们只需要合并时用下面的方法合并:
s u m i = s u m l s o n + ( n + 1 ) s i z l s o n v a l i + ( n + 1 ) s i z l s o n + 1 v a l r s o n sum_{i}=sum_{lson}+(n+1)^{siz_{lson}}val_{i}+(n+1)^{siz_{lson+1}}val_{rson} sumi=sumlson+(n+1)sizlsonvali+(n+1)sizlson+1valrson
每次比较当前的 h a s h hash hash值与我们目标序列的 h a s h hash hash值是否相同即可。

上面方法明显复杂度时 O ( n log ⁡   n ) O\left(n\log\,n\right) O(nlogn),但实测会 T T T, 都怪这**出题人卡常, 考虑优化。
平衡树不行我们还可以用值域线段树,我们只需要将区间的 s i z siz siz改为该区间中有值的点的个数,合并时将左区间的 s i z siz siz做次方项乘带右区间上即可。

时间复杂度依然是 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn),但实际上快了一倍多。

源码

然而线段树会被 64 M B 64MB 64MB卡空间,我刚好 65 M B 65MB 65MB,需要将线段树的大小设为 3 n + 5 3n+5 3n+5,可能不大严谨,大不了动态开点。

#include<bits/stdc++.h> 
using namespace std;
#define MAXN 1000005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const int mo=998244353;
const int inv2=499122177;
const int zero=10000;
const int lim=30000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int n,m,a[MAXN],b[MAXN],c[MAXN],pown[MAXN],sump[MAXN],mban,d[MAXN];
vector<int>ans;
struct ming{
	int lzy,siz,sum;
	ming(){siz=lzy=sum=0;}
};
class SegmentTree{
	private:
		ming tr[MAXN*3+5];	
	public:
		void pushup(int rt){
			tr[rt].siz=tr[lson].siz+tr[rson].siz;
			tr[rt].sum=add(tr[lson].sum,1ll*pown[tr[lson].siz]*tr[rson].sum%mo,mo);
		}
		void pushdown(int rt){
			if(!tr[rt].lzy)return ;
			if(tr[lson].siz)tr[lson].lzy=add(tr[rt].lzy,tr[lson].lzy,mo),
				tr[lson].sum=add(tr[lson].sum,1ll*(mo-tr[rt].lzy)*sump[tr[lson].siz-1]%mo,mo);
			if(tr[rson].siz)tr[rson].lzy=add(tr[rt].lzy,tr[rson].lzy,mo),
				tr[rson].sum=add(tr[rson].sum,1ll*(mo-tr[rt].lzy)*sump[tr[rson].siz-1]%mo,mo);
			tr[rt].lzy=0;
		}
		void insert(int rt,int l,int r,int ai,int aw){
			if(l>r||l>ai||r<ai)return ;
			if(l==r){
				if(!aw)tr[rt].siz=tr[rt].sum=0;
				else tr[rt].siz=1,tr[rt].sum=aw;
				return ;
			}
			int mid=l+r>>1;pushdown(rt);
			if(ai<=mid)insert(lson,l,mid,ai,aw);
			if(ai>mid)insert(rson,mid+1,r,ai,aw);
			pushup(rt);
		}
		void modify(){
			tr[1].lzy=add(tr[1].lzy,1,mo);
			if(n>1)tr[1].sum=add(tr[1].sum,mo-sump[n-2],mo);
		}
		int query(){return tr[1].sum;}
}T;
signed main(){
	read(n);read(m);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=m;i++)read(b[i]),d[i]=b[i];
	pown[0]=1;for(int i=1;i<=n;i++)pown[i]=1ll*pown[i-1]*(n+1)%mo;
	sump[0]=1;for(int i=1;i<=n;i++)sump[i]=add(sump[i-1],pown[i],mo);
	for(int i=1;i<=n;i++)mban=add(mban,1ll*pown[i-1]*a[i]%mo,mo);
	sort(d+1,d+m+1);for(int i=1;i<=m;i++)b[i]=lower_bound(d+1,d+m+1,b[i])-d;
	for(int i=1;i<n;i++)T.insert(1,1,m,b[i],i);
	for(int i=1;i<=m-n+1;i++){
		T.insert(1,1,m,b[i+n-1],n);
		if(T.query()==mban)ans.pb(i);
		T.insert(1,1,m,b[i],0);T.modify();
	}
	printf("%d\n",ans.size());
	for(int i=0;i<ans.size();i++)printf("%d ",ans[i]);puts("");
	return 0;
}

谢谢!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值