F. Paper Grading
大佬题解
一般关于前缀的问题基本都是Trie树。
首先将所给字符串建立一棵Trie树,Trie能够解决一个字符串在一个字符串集合中出现的次数,而查询前缀次数只需要找到Trie树中所给字符末尾的位置,那么其子树中打标记的次数即前缀次数。
由于子树dfs序[L,R]连续,于是把字典树按照dfs序标记,即变成区间查询问题[l,r]中[L,R]之间数的个数,二位偏序问题。
树套树(树状数组套下标线段树)即可解决,要动态开点!
#include<iostream>
using namespace std;
const int N=200010;
char s[N];
int n,q,pos[N];
// Trie树
struct T1
{int tree[N][26],idx;int insert(char s[]){int p=0;for(int i=0;s[i];i++){int t=s[i]-'a';if(!tree[p][t]) tree[p][t]=++idx;p=tree[p][t];}return p;}int find(char s[],int k){int p=0;for(int i=0;i<k;i++){int t=s[i]-'a';if(!tree[p][t]) return -1;p=tree[p][t];}return p;}
}Trie;
// 动态开点线段树
struct T2
{struct node{int l,r;int sz;}tree[N*40];int root[N],cnt;void update(int &u,int l,int r,int pos,int x){if(!u) u=++cnt;tree[u].sz+=x;if(l==r) return;int mid=l+r>>1;if(pos<=mid) update(tree[u].l,l,mid,pos,x);else update(tree[u].r,mid+1,r,pos,x);}int query(int u,int l,int r,int L,int R){if(!u) return 0;if(L<=l&&r<=R) return tree[u].sz;int mid=l+r>>1;int v=0;if(L<=mid) v+=query(tree[u].l,l,mid,L,R);if(R>mid) v+=query(tree[u].r,mid+1,r,L,R);return v;}
}Segment;
// dfs序转化为区间
int dfn[N],sz[N],timestamp;
void dfs(int u)
{dfn[u]=++timestamp;sz[u]=1;for(int i=0;i<26;i++)if(Trie.tree[u][i]) dfs(Trie.tree[u][i]),sz[u]+=sz[Trie.tree[u][i]];
}
// 树状数组
int lowbit(int x) {return x&-x;}
void add(int k,int pos,int x)
{for(;k<=n;k+=lowbit(k))Segment.update(Segment.root[k],1,timestamp,pos,x);
}
int sum(int k,int L,int R)
{int res=0;for(;k;k-=lowbit(k))res+=Segment.query(Segment.root[k],1,timestamp,L,R);return res;
}
int main()
{cin>>n>>q;for(int i=1;i<=n;i++){cin>>s;pos[i]=Trie.insert(s);}dfs(0);for(int i=1;i<=n;i++)add(i,dfn[pos[i]],1);while(q--){int op;cin>>op;if(op==1){int u,v;cin>>u>>v;add(u,dfn[pos[u]],-1);add(v,dfn[pos[v]],-1);add(u,dfn[pos[v]],1);add(v,dfn[pos[u]],1);swap(pos[u],pos[v]);}else{int k,l,r;cin>>s;cin>>k>>l>>r;int u=Trie.find(s,k);if(u==-1) cout<<0<<'\n';else{int L=dfn[u],R=dfn[u]+sz[u]-1;cout<<sum(r,L,R)-sum(l-1,L,R)<<'\n';}}}return 0;
}
cdq分治,带修改二维数点,把时间轴当作一维即静态三维数点,cdq分治+树状数组
#include<iostream>
#include<algorithm>
using namespace std;
const int N=200010;
char s[N];
int n,m,pos[N];
// Trie树
struct T1
{int tree[N][26],idx;int insert(char s[]){int p=0;for(int i=0;s[i];i++){int t=s[i]-'a';if(!tree[p][t]) tree[p][t]=++idx;p=tree[p][t];}return p;}int find(char s[],int k){int p=0;for(int i=0;i<k;i++){int t=s[i]-'a';if(!tree[p][t]) return -1;p=tree[p][t];}return p;}
}Trie;
// dfs序转化为区间
int dfn[N],sz[N],timestamp;
void dfs(int u)
{dfn[u]=++timestamp;sz[u]=1;for(int i=0;i<26;i++)if(Trie.tree[u][i]) dfs(Trie.tree[u][i]),sz[u]+=sz[Trie.tree[u][i]];
}
int ans[N];
struct node
{int op;int a,b,c,cnt;int sign,id;
}q[N*5];
int st[N];
bool cmpb(const node &x,const node &y)
{return x.b<y.b||x.b==y.b&&x.op<y.op;
}
int fw[N];
int lowbit(int x){return x&-x;}
void update(int k,int x){for(;k<=timestamp;k+=lowbit(k)) fw[k]+=x;}
int query(int k){int res=0;for(;k;k-=lowbit(k)) res+=fw[k];return res;}
void solve(int l,int r)
{if(l>=r) return;int mid=l+r>>1;solve(l,mid),solve(mid+1,r);int i=l;for(int j=mid+1;j<=r;j++){if(q[j].op==1) continue;while(i<=mid&&q[i].b<=q[j].b){if(q[i].op==1) update(q[i].c,q[i].cnt);i++;}ans[q[j].id]+=q[j].sign*query(q[j].c);}while(i>l){i--;if(q[i].op==1) update(q[i].c,-q[i].cnt);}inplace_merge(q+l,q+mid+1,q+r+1,cmpb);
}
int main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>s;pos[i]=Trie.insert(s);}dfs(0);int cnt=0;for(int i=1;i<=n;i++)q[++cnt]={1,0,i,dfn[pos[i]],1};for(int i=1;i<=m;i++){int op;cin>>op;if(op==1){int u,v;cin>>u>>v;q[++cnt]={1,i,u,dfn[pos[u]],-1};q[++cnt]={1,i,v,dfn[pos[v]],-1};q[++cnt]={1,i,u,dfn[pos[v]],+1};q[++cnt]={1,i,v,dfn[pos[u]],+1};swap(pos[u],pos[v]);}else{st[i]=1;int k,l,r;cin>>s;cin>>k>>l>>r;int u=Trie.find(s,k);if(u==-1) ans[i]=0;else{int L=dfn[u],R=dfn[u]+sz[u]-1;q[++cnt]={2,i,r,R,0,1,i};q[++cnt]={2,i,l-1,R,0,-1,i};q[++cnt]={2,i,r,L-1,0,-1,i};q[++cnt]={2,i,l-1,L-1,0,1,i};}}}solve(1,cnt);for(int i=1;i<=m;i++)if(st[i]) cout<<ans[i]<<'\n';return 0;
}
写代码过程中总是弄不清记得东西,每次都是一层一层的想,尤其是dfs序问题,很迷糊,还是要多写,多积累!!!要不然训练总是挂机