• [冲刺国赛2022] 字符串


    一、题目

    给定一个长度为 \(n\) 的字符串 \(S\),有 \(m\) 次询问 \((x,y)\),问这个串长度为 \(x\) 的前缀和长度为 \(y\) 的后缀连接成的新串 \(T\)\(S\) 中的出现次数。

    \(n,m\leq 2\cdot 10^5\)

    二、解法

    考虑子串 \(S[l,r]\) 对询问 \((x,y)\) 产生贡献的充要条件是:\(S[1,x]=S[l,l+x-1]\and S[n-y+1,n]=S[r-y+1,r]\)

    那么可以把两个条件拆开,只需要加上 \(l+x=r-y+1\) 的限制即可。拆开后发现两边都是 \(border\) 的形式,所以可以对正串和反串都做一次 \(\tt kmp\),把 \(i\)\(border\) 设置为 \(i\) 的父亲,那么 \(x\) 的子树内就是它所有可能的出现位置。

    问题可以转化成:问 \(y\) 子树内有多少个点 \(z\),使得 \(z\) 的前一个点在 \(x\) 子树中,可以扫描线 + 树状数组解决。

    时间复杂度 \(O(n\log n)\)

    三、总结

    分析字符串问题时,要列出具体的等式关系。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 200005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,m,k,nxt[M],b[M];char s[M];
    int ans[M],i1[M],o1[M],i2[M],o2[M],id[M];
    vector<int> g1[M],g2[M];
    struct node{int l,r,id;};vector<node> A[M],B[M];
    void kmp()
    {
    	for(int i=2,j=0;i<=n;i++)
    	{
    		while(j && s[j+1]^s[i]) j=nxt[j];
    		if(s[j+1]==s[i]) j++;
    		nxt[i]=j;
    	}
    }
    void dfs1(int u)
    {
    	if(u>0) i1[u]=++k;
    	for(int v:g1[u]) dfs1(v);
    	if(u>0) o1[u]=k;
    }
    void dfs2(int u)
    {
    	if(u<=n) i2[u]=++k;
    	for(int v:g2[u]) dfs2(v);
    	if(u<=n) o2[u]=k;
    }
    void add(int x)
    {
    	for(int i=x;i<=n;i+=i&(-i)) b[i]++;
    }
    int ask(int x)
    {
    	int r=0;
    	for(int i=x;i>0;i-=i&(-i)) r+=b[i];
    	return r;
    }
    int ask(int l,int r)
    {
    	return ask(r)-ask(l-1);
    }
    void work()
    {
    	n=read();m=read();scanf("%s",s+1);
    	for(int i=0;i<=n+1;i++)
    		g1[i].clear(),g2[i].clear(),
    		A[i].clear(),B[i].clear();
    	kmp();
    	for(int i=1;i<=n;i++)
    		g1[nxt[i]].push_back(i);
    	reverse(s+1,s+1+n);
    	kmp();
    	for(int i=1;i<=n;i++)
    		g2[n-nxt[i]+1].push_back(n-i+1);
    	k=0;dfs1(0);
    	k=0;dfs2(n+1);
    	for(int i=1;i<=n;i++) id[i2[i]]=i;
    	//
    	for(int i=1;i<=m;i++)
    	{
    		int x=read(),y=read();ans[i]=0;
    		A[i2[n-y+1]-1].push_back({i1[x],o1[x],i});
    		B[o2[n-y+1]].push_back({i1[x],o1[x],i});
    	}
    	for(int i=1;i<=n;i++) b[i]=0;
    	for(int i=1;i<=n;i++)
    	{
    		if(id[i]!=1) add(i1[id[i]-1]);
    		for(auto [l,r,id]:A[i]) ans[id]-=ask(l,r);
    		for(auto [l,r,id]:B[i]) ans[id]+=ask(l,r);
    	}
    	for(int i=1;i<=m;i++)
    		printf("%d\n",ans[i]);
    }
    signed main()
    {
    	freopen("string.in","r",stdin);
    	freopen("string.out","w",stdout);
    	T=read();
    	while(T--) work();
    }
    
  • 相关阅读:
    python 查看源代码
    团队项目5-冲刺合集
    系统设计(团队作业4)
    《次元唤醒 需求规格说明书v1.0》
    团队选题报告
    来自异次元的一篇博客
    《口算大作战 概念版》功能规格说明书
    我不会优化啊!!!
    Python装饰器实现异步回调
    Python杀死windows进程
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16443557.html
Copyright © 2020-2023  润新知