4556: [Tjoi2016&Heoi2016]字符串

本文介绍了一种解决字符串问题的方法:通过构建后缀自动机并结合线段树进行高效查询。具体实现包括字符串反序、后缀自动机构建、线段树合并及倍增数组的应用。

字符串题不会做先想能不能把字符串反过来

把字符串反序,建立后缀自动机,利用线段树合并算出每个位置的right集

二分答案,用树上倍增找到对应的节点,看是否有[a+mid-1,b]中的数在right集中

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
 
#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 200010
using namespace std;
int ch[N][26],fail[N],c[N],q[N],fa[N][20],len[N],root[N],pos[N];
int son[8000010][2];
char st[N];
int tnt,cnt=1,last=1,n;
 
void sig_ins(int &i,int l,int r,int x)
{
i=++tnt;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) sig_ins(son[i][0],l,mid,x);
else sig_ins(son[i][1],mid+1,r,x);
}
 
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
int z=++tnt;
son[z][0]=merge(son[x][0],son[y][0]);
son[z][1]=merge(son[x][1],son[y][1]);
return z;
}
 
bool find(int x,int l,int r,int ql,int qr)
{
if (!x) return 0;
if (ql<=l&&r<=qr) return 1;
int mid=(l+r)>>1;
if (ql<=mid&&find(son[x][0],l,mid,ql,qr)) return 1;
if (mid+1<=qr&&find(son[x][1],mid+1,r,ql,qr)) return 1;
return 0;
}
int insert(int c,int id)
{
int p=last,x=last=++cnt; len[x]=len[p]+1;
sig_ins(root[x],1,n,id);
while (p&&!ch[p][c]) ch[p][c]=x,p=fail[p];
if (!p) fail[x]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fail[x]=q;
else
{
int cpy=++cnt; len[cpy]=len[p]+1;
memcpy(ch[cpy],ch[q],sizeof(ch[q]));
fail[cpy]=fail[q]; fail[x]=fail[q]=cpy;
while (p&&ch[p][c]==q) ch[p][c]=cpy,p=fail[p];
}
}
return x;
}
 
void get_fail()
{
for (int i=1;i<=cnt;i++) c[len[i]]++;
for (int i=1;i<=cnt;i++) c[i]+=c[i-1];
for (int i=cnt;i;i--) q[c[len[i]]--]=i;
for (int i=cnt;i;i--)
{
int x=q[i],f=fail[x];
root[f]=merge(root[f],root[x]);
}
for (int i=1;i<=cnt;i++) fa[i][0]=fail[i];
for (int j=1;j<=18;j++)
for (int i=1;i<=cnt;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
 
bool ok(int mid,int x,int L,int R)
{
L=L+mid-1; if (L>R) return 0;
for (int j=18;j>=0;j--)
if (len[fa[x][j]]>=mid) x=fa[x][j];
return find(root[x],1,n,L,R);
}
int main()
{
//printf("%d\n",(sizeof(fa)+sizeof(ch)+sizeof(c)*6+sizeof(son))/1024/1024);
int m; scanf("%d%d",&n,&m);
scanf("%s",st+1); reverse(st+1,st+n+1);
for (int i=1;i<=n;i++) pos[i]=insert(st[i]-'a',i);
get_fail();
for (int i=1;i<=m;i++)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
a=n-a+1; b=n-b+1; c=n-c+1; d=n-d+1; swap(a,b); swap(c,d);
int l=0,r=d-c+1;
while (l!=r)
{
int mid=(l+r+1)>>1;
if (ok(mid,pos[d],a,b)) l=mid; else r=mid-1;
}
printf("%d\n",l);
}
return 0;
}
/*
字符串从1开始
把字符串反序,询问[l,r]相当于[n-r+1,n-l+1]
建出后缀自动机,对fail树建好倍增数组,用可持久化的方式进行线段树合并
二分答案len l=0 r= d-c+1
找到点满足len>=mid,然后查询是否存在[a,b-len+1]是否存在
找存在的最大值
*/

### HEOI2016TJOI2016 竞赛中的树相关数据结构问题 #### 1. 树链剖分的应用 对于涉及树的数据结构问题,树链剖分是一种非常有效的技术。通过将树分解成若干条重路径和轻边,可以在 \(O(\log n)\) 的时间复杂度内处理树上的查询和更新操作[^1]。 ```cpp void dfs1(int u, int f, int d) { fa[u] = f; dep[u] = d; siz[u] = 1; son[u] = 0; for (auto v : G[u]) { if (v == f) continue; w[v] = ++tot; top[tot] = v; dfs1(v, u, d + 1); siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } } ``` 此代码片段展示了如何利用深度优先搜索(DFS)来初始化树的相关属性,如父节点、深度、子树大小等,这些信息是后续实现树链剖分的基础。 #### 2. 动态开点线段树优化 针对某些特定场景下的动态区间修改与查询需求,采用动态开点线段树能够有效降低空间消耗并提高效率。这种方法特别适用于值域较大而实际使用的范围较小的情况,在这类情况下静态分配内存可能导致浪费过多资源[^3]。 #### 3. 倍增算法求LCA 倍增法用于快速计算两点之间的最近公共祖先(Lowest Common Ancestor),其核心思想是在预处理阶段记录每个结点向上跳转\(2^i\)步后的父亲位置,从而使得每次查找的时间复杂度降为常数级别[^5]。 ```cpp for (int j = 1; j <= max_level; ++j) for (int i = 1; i <= n; ++i) dp[i][j] = dp[dp[i][j - 1]][j - 1]; // 查询u,v的lca while (dep[u] != dep[v]) { if (dep[u] < dep[v]) swap(u, v); for (int k = max_level; ~k; --k) if ((1 << k) & (dep[u] - dep[v])) u = dp[u][k]; } if (u == v) return u; for (int k = max_level; ~k; --k) if (dp[u][k] ^ dp[v][k]) u = dp[u][k], v = dp[v][k]; return dp[u][0]; ``` 这段代码实现了基于倍增原理的LCA查询功能,其中`max_level`表示最大可能跳跃次数,通常取值不超过20即可满足大多数情况的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值