Trie
字典树\(trie\),用于检索字符串是否出现过,也可以检索前缀,对于字符串的\(trie\),每个点拥有\(26\)个儿子,具有相同前缀的字符串共用前缀节点,整棵树的根节点为空,默认只有小写字母。
一般而言,\(root\)和\(tot\)是可以初始化为\(0\)的,但是特殊情况下需要初始化为\(1\),故多数时候初始化为\(1\)更为保险。
对于可持久化\(trie\),不要用\(root[0]\)来计数,虽然方便,但是当出现根为\(0\)的情况时会出错。
struct Trie{
struct tree{
int ch[26],sum/*前缀和*/,cnt/*完整出现次数*/,v/*附加权值*/;
}t[N*26];
int root,tot;
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sum)
#define c(p) (t[p].cnt)
#define v(p) (t[p].v)
inline void insert(string s,int v=0){/*插入字符串*/
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))ch(p,c)=++tot;/*新建一个节点*/
p=ch(p,c);
s(p)++;/*前缀出现次数累加*/
}
c(p)++;/*整个串出现次数累加*/
v(p)=v;/*赋值*/
}
inline bool exist(string s){/*判断存在*/
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))return false;/*不存在孩子则整个串没有出现*/
p=ch(p,c);
}
return c(p)!=false;
}
inline int count(string s){/*统计出现次数*/
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))return 0;
p=ch(p,c);
}
return c(p);
}
inline int countprefix(string s){/*统计s作为前缀出现的次数*/
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))return 0;
p=ch(p,c);
}
return s(p);
}
inline int findval(string s){/*寻找s的附加权值*/
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))return 0;
p=ch(p,c);
}
return v(p);
}
inline bool del(string s){/*删除一个字符串*/
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))return false;/*未出现过则删除失败*/
p=ch(p,c);
}
int cnt=c(p);/*记录这个串出现的次数*/
p=0;
for(int i=0;i<n;i++){
int c=s[i]-'a';
p=ch(p,c);
s(p)-=cnt;/*将这个串的前缀全部删掉这个串出现的次数*/
}
c(p)=0;/*清空这个串的出现次数*/
v(p)=0;/*清空附加权值*/
for(int i=0;i<26;i++)ch(p,i)=0;/*清空子节点*/
return true;
}
};
节点存储字符串,代表以该子树内字符开头的最长公共前缀。
class Trie{
struct tree{
const char*s;/*指向存储字符串的首元素*/
int sz/*存储字符串的大小*/,cnt/*出现次数*/,v/*附加权值*/;
tree*ch[26];/*子节点*/
inline tree(){
memset(this,0,sizeof(*this));
}
inline tree(const char*str,int len=0,int num=0,int val=0){
s=str;
sz=len;
cnt=num;
v=val;
memset(ch,0,sizeof(ch));
}
}root;
void insert(const char*s,int sz,tree*p,int v=0){
if(sz==0){/*递归边界*/
p->cnt++;/*累加次数*/
p->v=v;/*权值赋值*/
return;
}
int c=s[0]-'a';
if(p->ch[c]==0){/*不存在子节点*/
p->ch[c]=new tree(s,sz,1,v);/*新建并赋值*/
return;
}
p=p->ch[c];
int k=0;
while(k<sz&&k<p->sz&&p->s[k]==s[k])k++;/*循环找到当前字符串和节点存储的字符串的最长前缀*/
if(k<p->sz){/*一个新字符串*/
tree*q=new tree(p->s+k,p->sz-k,p->cnt,p->v);/*拆分已存储字符串*/
memcpy(q->ch,p->ch,sizeof(q->ch));/*继承信息*/
memset(p->ch,0,sizeof(p->ch));
p->cnt=0;
p->v=0;/*清空*/
p->ch[p->s[k]-'a']=q;/*重新初始化*/
}
p->sz=k;/*修改长度*/
insert(s+k,sz-k,p,v);
}
void del(const char*s,int sz,tree*p){
if(sz==0){
p->cnt=0;
p->v=0;
return;
}
int c=s[0]-'a',k=0;
p=p->ch[c];
while(k<p->sz&&p->s[k]==s[k])k++;
del(s+k,sz-k,p);
}
bool exist(const char*s,int sz,tree*p){
if(sz==0)return p->cnt!=false;/*递归边界*/
int c=s[0]-'a';
if(p->ch[c]==0)return false;/*不存在的孩子*/
p=p->ch[c];
if(sz<p->sz)return false;/*字符串小于当前最长公共前缀*/
int k=0;
while(k<p->sz&&p->s[k]==s[k])k++;
if(k<p->sz)return false;/*最长公共前缀必须是查找的字符串的子串*/
return exist(s+k,sz-k,p);
}
int count(const char*s,int sz,tree*p){
if(sz==0)return p->cnt;
int c=s[0]-'a';
if(p->ch[c]==0)return 0;
p=p->ch[c];
if(sz<p->sz)return 0;
int k=0;
while(k<p->sz&&p->s[k]==s[k])k++;
if(k!=p->sz)return 0;
return count(s+k,sz-k,p);
}
int findval(const char*s,int sz,tree*p){
if(sz==0)return p->v;
int c=s[0]-'a';
if(p->ch[c]==0)return 0;
p=p->ch[c];
if(sz<p->sz)return 0;
int k=0;
while(k<p->sz&&p->s[k]==s[k])k++;
if(k!=p->sz)return 0;
return findval(s+k,sz-k,p);
}
public:
inline void insert(const char*s,int v=0){
insert(s,strlen(s),&root,v);
}
inline void del(const char*s){
del(s,strlen(s),&root);
}
inline bool exist(const char*s){
return exist(s,strlen(s),&root);
}
inline int count(const char*s){
return count(s,strlen(s),&root);
}
inline int findval(const char*s){
return findval(s,strlen(s),&root);
}
};
可持久化\(trie\),每次在插入时复制一条链。
一棵由字符串构成的树询问两点见路径上上有多少字符串以\(s\)为前缀。
\(dfs\)时插入字符串,由于每个字符串是建立在父节点的基础上,可以用可持久化\(trie\),答案具有可减性,求\(LCA\)后差分即可。
inline void insert(string s,int q){/*版本来源*/
int p=root[++top]=++tot/*新建版本*/,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
t[p]=t[q];/*继承信息*/
s(p)++;/*前缀和累加*/
ch(p,c)=++tot;/*新建节点*/
p=ch(p,c);
q=ch(q,c);
}
}
void dfs(int x,int f){
rt[x]=root[top];
for(int i=1;i<=20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
dep[x]=dep[f]+1;
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==f)continue;
insert(e[i].s,rt[x]);
fa[y][0]=x;
dfs(y,x);
}
}
\(01trie\)实现平衡树,基于二进制拆分,维护二进制路径上的数量。
struct Trie{
struct tree{
int ch[2],sum;
}t[N*32];
int root=1,tot=1,INF=1e9;
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sum)
inline void insert(int x,int v){
int p=root;
x+=INF;
for(int i=31;~i;i--){
int c=x>>i&1;
if(!ch(p,c))ch(p,c)=++tot;
p=ch(p,c);
s(p)+=v;
}
}
inline int rank(int x){
int p=root,re=1;/*实质是小于x的数的个数,所以re初始化为1*/
x+=INF;
for(int i=31;~i;i--){
int c=x>>i&1;
if(c)re+=s(l(p));/*这一位为1,累加这一位0的个数*/
p=ch(p,c);
}
return re;
}
inline int kth(int x){
int p=root,re=0;
for(int i=31;~i;i--){
if(x>s(l(p))){/*左子树大小无法满足x,计入答案并更新x,要求严格大于,不然后面相等时会一直计入答案*/
x-=s(l(p));
re|=1<<i;
p=r(p);
}
else p=l(p);
}
return re-INF;
}
inline bool exist(int x){
int p=root;
x+=INF;
for(int i=31;~i;i--){
int c=x>>i&1;
p=ch(p,c);
if(!p)return false;
}
return true;
}
inline void insert(int x){
insert(x,1);
}
inline void del(int x){
if(exist(x))insert(x,-1);
}
inline int pre(int x){
return kth(rank(x)-1);
}
inline int suc(int x){
return kth(rank(x+1));
}
};
可持久化\(01trie\)实现可持久化平衡树。
struct Trie{
struct tree{
int ch[2],sum;
}t[N*32];
int root[N],top,tot,INF=1e9;
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sum)
inline void ins(int&x,int v,int delta){
int q=x/*版本来源*/,p=x=++tot/*新建版本*/;
v+=INF;/*统一基础值*/
for(int i=31;~i;i--){
int c=v>>i&1;
ch(p,c)=++tot;/*新建*/
ch(p,c^1)=ch(q,c^1);/*继承*/
p=ch(p,c);
q=ch(q,c);
s(p)=s(q)+delta;/*前缀更新*/
}
}
inline int rnk(int p,int x){
int re=1;
x+=INF;
for(int i=31;~i;i--){
int c=x>>i&1;
if(c)re+=s(l(p));
p=ch(p,c);
}
return re;
}
inline int kt(int p,int x){
int re=0;
for(int i=31;~i;i--){
if(x>s(l(p))){
re|=1<<i;
x-=s(l(p));
p=r(p);
}
else p=l(p);
}
return re-INF;
}
inline bool exis(int p,int x){
x+=INF;
for(int i=31;~i;i--){
int c=x>>i&1;
p=ch(p,c);
if(!p)return false;
}
return true;
}
inline void modify(int x,int y){
root[x]=root[y];
}
inline void insert(int p,int x){
ins(root[p],x,1);
}
inline void del(int p,int x){
if(exis(root[p],x))ins(root[p],x,-1);
}
inline int rank(int p,int x){
return rnk(root[p],x);
}
inline int kth(int p,int x){
return kt(root[p],x);
}
inline int pre(int p,int x){
return kt(root[p],rnk(root[p],x)-1);
}
inline int suc(int p,int x){
return kt(root[p],rnk(root[p],x+1));
}
inline bool exist(int p,int x){
return exis(root[p],x);
}
};
可持久化\(01trie\)实现可持久化平衡树,查询静态区间第k大。
struct Trie{
struct tree{
int ch[2],sum;
}t[N<<6];
int tot,root[N];
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sum)
inline void insert(int p,int x){
root[p]=++tot;/*新建根节点*/
int q=root[p-1];/*前一个数的根节点*/
p=root[p];
for(int i=31;~i;i--){
int c=x>>i&1;
ch(p,c)=++tot;/*新建*/
ch(p,c^1)=ch(q,c^1);/*复制*/
p=ch(p,c);
q=ch(q,c);
s(p)=s(q)+1;/*增加前缀和*/
}
}
inline int kth(int l,int r,int k){
int re=0,p=root[r],q=root[l-1];
for(int i=31;~i;i--){
int x=s(l(p))-s(l(q));/*区间内的差值*/
if(k>x){/*左子树不够*/
re|=1<<i;/*累加答案*/
k-=x;/*进入右子树时更新k*/
p=r(p);/*进入右子树*/
q=r(q);
}
else{/*进入左子树*/
p=l(p);
q=l(q);
}
}
return re;
}
}t;
压缩\(01trie\)实现平衡树。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
struct CompressedTrie{
struct tree{
int ch[2],dep,v,pt,sz;
}t[N];
int root=1,tot=1,base=1e7,lim=31;
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define d(p) (t[p].dep)
#define v(p) (t[p].v)
#define p(x) (t[x].pt)
#define s(p) (t[p].sz)
inline int pos(int x,int k){
return x>>k&1;/*获取x从低到高第k位*/
}
inline int reverse(int x){/*翻转x*/
int ans=0;
for(int i=0;i<lim;i++)ans|=pos(x,i)<<(lim-i-1);
return ans;
}
inline int create(int p,int dep,int x=0,int delta=0){
t[++tot]={0,0,dep,x,p,delta};
return tot;
}
void insert(int x,int delta){
int v=x,p=root,k=0,last=0;
x=reverse(x);
for(int i=0;i<lim;i++){
k++;
int c1=pos(x,i);
while(i>d(p)){/*第一个新建节点i=0要求t[0].dep=-1*/
if(!ch(p,c1)){/*未出现则新建*/
ch(p,c1)=create(x>>i,lim,v,delta);/*新建一个深度为lim的权值为v的前缀和为delta的节点*/
return;
}
last=p;
k=i-d(p);
p=ch(p,c1);
s(last)+=delta;
}
int c2=pos(p(p),k-1);
if(c1!=c2){
int q=create(p(p),i-1);
s(q)=s(p)+delta;
ch(q,c2)=p;
ch(q,c1)=create(x>>i,lim);
ch(last,p(p)&1)=q;
p(p)>>=(k-1);
last=q;
p=ch(q,c1);
k=1;
s(p)+=delta;
v(p)=v;
return;
}
}
s(p)+=delta;
}
inline int rank(int x){
insert(x,0);
x=reverse(x);
int p=root,ans=0;
for(int i=0;i<lim;i++){
int c=pos(x,i);
while(i>d(p)){
if(c)ans+=s(l(p));
p=ch(p,c);
}
}
return ans;
}
inline int kth(int x){
int p=root;
while(l(p)||r(p)){
if(x<=s(l(p)))p=l(p);
else x-=s(l(p)),p=r(p);
}
return v(p);
}
inline void insert(int x){
insert(x,1);
}
inline void del(int x){
insert(x,-1);
}
inline CompressedTrie(){
d(1)=-1;
}
}t;
int main(){
int n;
int base=1e9;
cin>>n;
while(n--){
int op,x;
cin>>op>>x;
switch(op){
case 1:t.insert(x+base);break;
case 2:t.del(x+base);break;
case 3:cout<<t.rank(x+base)+1<<'\n';break;
case 4:cout<<t.kth(x)-base<<'\n';break;
case 5:cout<<t.kth(t.rank(x+base))-base<<'\n';break;
case 6:cout<<t.kth(t.rank(x+1+base)+1)-base<<'\n';break;
}
}
return 0;
}
可持久化\(01trie\),一般用于区间的异或和操作,版本i代表在插入\(a[i]\)之后的\(trie\)树。
支持两种操作,在序列末尾添加一个数\(x\),查询\(x\)和\([l,r]\),找到一个\(p\)在\([l,r]\),使得\(a[p]^a[p+1]^...a[n]^x\)最大。
转化成异或前缀和形式,原式等于\(s[p-1]^s[n]^x\),对于\(s[n]^x\)是定值,贪心查找\([l,r]\)对应的\(sum\)差,只要不为\(0\)就说明这一段有异或前缀。
struct Trie{
struct tree{
int ch[2],sum;
}t[N<<5];
int root[N],tot,top;
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sum)
inline void insert(int x){
int q=root[top],p=root[++top]=++tot;
for(int i=31;~i;i--){
int c=x>>i&1;
ch(p,c^1)=ch(q,c^1);
if(!ch(p,c))ch(p,c)=++tot;
p=ch(p,c);
q=ch(q,c);
s(p)=s(q)+1;
}
}
inline int query(int x,int l,int r){
int q=root[l-1],p=root[r],re=0;
for(int i=31;~i;i--){
int c=x>>i&1;
if(s(ch(p,c^1))>s(ch(q,c^1)))re|=1<<i,c^=1;
p=ch(p,c);
q=ch(q,c);
}
return re;
}
}t;
t.insert(0);
for(int i=1;i<=n;i++){
int x;
cin>>x;
sum^=x;
t.insert(sum);
}
while(q--){
string s;
cin>>s;
if(s=="A"){
int x;
cin>>x;
sum^=x;
t.insert(sum);
}
else{
int l,r,x;
cin>>l>>r>>x;
cout<<t.query(sum^x,l,r)<<'\n';
}
}
给定序列\(a\),选取一段区间\([l,r]\),贡献为区间最大值与区间次大值的异或值,问贡献的最大值。
枚举每个点作为次大值的情况,设左边第一个比它大的下标为\(l_1\),第二个为\(l_2\),右边同理,取它作为次大值的区间\([l_1+1,r_2-1]\)和\([l_2+1,r_1-1]\),其他区间为这两个的子集,元素从小到大依次删除,链表处理,区间内的\(trie\)上贪心。
inline int query(int x,int l,int r){
int re=0,q=root[l],p=root[r];
for(int i=31;~i;i--){
int c=x>>i&1;
if(s(ch(p,c^1))>s(ch(q,c^1)))re|=(1<<i),c^=1;
p=ch(p,c);
q=ch(q,c);
}
return re;
}
}trie;
int pre[N],nxt[N],p[N],a[N],ans;
inline bool cmp(int x,int y){
return a[x]<a[y];
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)pre[i]=i-1,nxt[i]=i+1,p[i]=i;
for(int i=1;i<=n;i++)cin>>a[i],trie.insert(a[i]);
sort(p+1,p+1+n,cmp);
/*
枚举次大值
x左边比他大的第一个为L1,第二个L2,
右边比他大的同理R1,R2
所选取区间为[L1+1,R2-1],[L2+1,R1-1]
链表将元素从小到大删除
trie贪心
边界似乎可以不判断???
*/
for(int i=1;i<=n;i++){
int l=pre[p[i]],r=nxt[p[i]];
nxt[l]=r;pre[r]=l;
ans=max(ans,trie.query(a[p[i]],pre[l],r-1));//左边
ans=max(ans,trie.query(a[p[i]],l,nxt[r]-1));//右边
}
\(01trie\),将数拆成二进制形式,之后插入,一般用于维护异或和,从高位到低位插入,便于贪心。
一棵带权树,问最大路径和。
求出每个点到根节点的异或和,将其插入\(01trie\)中,由于从最高位插入,每次贪心取与当前位不同的数即可。\(x\)到\(y\)的异或路径等价于根到\(x\)的异或路径再异或上根到\(y\)的异或路径。
inline int maxxor(int x){
int p=root,re=0;
for(int i=31;~i;i--){
int c=x>>i&1;
if(ch(p,c^1))p=ch(p,c^1),re|=1<<i;
else p=ch(p,c);
}
return re;
}
一个区间的贡献是区间元素的异或和,要选\(k\)个区间,且这\(k\)个去加你的贡献互不相同,问贡献和最大值。
异或前缀和之后,可以转化成对于关于三角的求值,\(a[i]^a[j]\),\(0<=i<=j<=n\),于是先将答案乘\(2\)最后再除回去,转化成求最大的\(2*k\)的有序对。对每个\(i\)求出第\(t\)大的\(a[i]^a[j]\),结果扔到堆里,每次取对顶并把对应的\(t+1\)大的\(a[i]^a[j]\)扔到堆里。
inline int query(int x,int k){//^x的第k大值
int p=root,ans=0;
for(int i=31;~i;i--){
int c=(x>>i)&1;
if(t[t[p].ch[c^1]].sum>=k)ans|=(1ll<<i),p=t[p].ch[c^1];
else k-=t[t[p].ch[c^1]].sum,p=t[p].ch[c];
}
return ans;
}
}t;
for(int i=0;i<=n;i++)t.insert(s[i]);
for(int i=0;i<=n;i++)q.push({i,1,t.query(s[i],1)});
while(k--){
node x=q.top();
q.pop();
ans+=x.v;
q.push({x.id,x.rk+1,t.query(s[x.id],x.rk+1)});
}
一棵树,支持三中操作,将树上与节点x距离为1的节点上的权值+1,在节点x上权值较少v,查询初上与节点x距离为1的所有点的权值异或和。维护异或和,只需要知道某一位上0和1的个数的奇偶性即可,trie上的权值维护一个数二进制路径上的出现次数,对于全局加一从低位到高位建trie,就是找到第一个出现的0,变成1,后面的1都变成0,trie上就是交换左右儿子,顺着交换后的0递归。
struct tree{
int ch[2],sum,v;
}t[N*30];
int root[N],tot;
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sum)
#define v(p) (t[p].v)
inline void pushup(int p){
v(p)=s(p)=0;
if(l(p)){
v(p)+=v(l(p));
s(p)^=s(l(p))<<1;
}
if(r(p)){
v(p)+=v(r(p));
s(p)^=(s(r(p))<<1)|(v(r(p))&1);
}
}
void insert(int&p,int x,int dep){
if(!p)p=++tot;
if(dep>20)return v(p)++,void();
insert(ch(p,x&1),x>>1,dep+1);
pushup(p);
}
void del(int p,int x,int dep){
if(dep>20)return v(p)--,void();
del(ch(p,x&1),x>>1,dep+1);
pushup(p);
}
void addall(int p){
swap(l(p),r(p));
if(l(p))addall(l(p));
pushup(p);
}
}t;
vector<int>v[N];
int fa[N],flag[N],a[N];
void dfs(int x,int f){
fa[x]=f;
for(auto y:v[x]){
if(y==f)continue;
dfs(y,x);
}
}
inline int find(int x){
return (fa[x]==-1?0:flag[fa[x]])+a[x];
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
v[a].push_back(b);
v[b].push_back(a);
}
dfs(1,-1);
for(int i=1;i<=n;i++){
cin>>a[i];
if(fa[i]!=-1)t.insert(t.root[fa[i]],a[i],0);
}
while(m--){
int op,x;
cin>>op>>x;
if(op==1){
flag[x]++;
if(x!=1){
if(fa[fa[x]]!=-1)t.del(t.root[fa[fa[x]]],find(fa[x]),0);
a[fa[x]]++;
if(fa[fa[x]]!=-1)t.insert(t.root[fa[fa[x]]],find(fa[x]),0);
}
t.addall(t.root[x]);
}
else if(op==2){
int y;
cin>>y;
if(x!=1)t.del(t.root[fa[x]],find(x),0);
a[x]-=y;
if(x!=1)t.insert(t.root[fa[x]],find(x),0);
}
else if(op==3){
int re=t.t[t.root[x]].sum;
re^=find(fa[x]);
cout<<re<<'\n';
}
}
支持两种操作,将所有a[i]加上v,其中i与y在mod(2x)时同余,查询所有a[i]的和,其中i与y在mod(2x)时同余。将下标看做二进制串,询问就是一些相同后缀的二进制,所以从低位到高位构建01trie。叶子节点维护当前二进制串的序列值,非叶子节点维护子树和。设根节点位0层,对于在第x层代表y的节点,信息就是所有对2^x取模后值为y的下标对应的序列和。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e6+6;
int a[N],n,m,x,y,z,la;
struct LazyTrie{
struct tree{
int ch[2],sz,v,add;
}t[N];
int root=1,tot=1;
#define l(p) (t[p].ch[0])
#define r(p) (t[p].ch[1])
#define ch(p,x) (t[p].ch[x])
#define s(p) (t[p].sz)
#define v(p) (t[p].v)
#define a(p) (t[p].add)
inline void insert(int x,int v){
int p=root;
v(p)+=v;
s(p)++;
for(int i=0;i<=20;i++){
int c=x>>i&1;
if(!ch(p,c))ch(p,c)=++tot;
p=ch(p,c);
v(p)+=v;
s(p)++;
}
}
inline void pushup(int p){
v(p)=v(l(p))+v(r(p));
}
inline void add(int p,int x){
if(!p)return;
v(p)+=x*s(p);
a(p)+=x;
}
inline void pushdown(int p){
if(!a(p))return;
add(l(p),a(p));
add(r(p),a(p));
a(p)=0;
}
void modify(int p,int x,int y,int v,int d=0){
if(!p)return;
if(d>=x)return add(p,v);
pushdown(p);
modify(ch(p,y>>d&1),x,y,v,d+1);
pushup(p);
}
int query(int p,int x,int y,int d=0){
if(!p)return 0;
if(d>=x)return v(p);
pushdown(p);
return query(ch(p,y>>d&1),x,y,d+1);
}
}t;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int x;
cin>>x;
t.insert(i,x);
}
int la=0;
while(m--){
int op,x,y,z;
cin>>op>>x>>y;
op=((op^la)&1)+1;
if(op==1)cin>>z,t.modify(1,x,y,z);
else cout<<(la=t.query(1,x,y))<<'\n';
}
return 0;
}
对于每个字符串,查找有多少个字符串与之前缀相同。插入trie中,维护前缀数和出现次数。
inline int find(const int*s,int sz){
int p=root,re=0;
for(int i=1;i<=sz;i++){
int c=a[i];
if(!ch(p,c))return re;
p=ch(p,c);
re+=c(p);//累加别人作为自己前缀的次数
}
return re-c(p)+s(p);//减去自己出现的次数,加上自己作为别人前缀的次数
}
给出一些字符串,问哪些字符串在重新排列后字典序最小,重新排列指的是排列一个字母表,a对应第一个,b对应第二个。对于一个字符串s,如果有字符串是当前字符串的子串,那么一定不可行。对于有相同前缀的字符,找到第一个不同的字符,并进行连边,若最后有环则无解。
inline void topo(){
while(!q.empty())q.pop();
for(int i=0;i<26;i++)if(!in[i])q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
for(int y=0;y<26;y++)if(e[x][y]&&--in[y]==0)q.push(y);
}
}
inline bool find(string s){
int p=root,n=s.size();
memset(e,0,sizeof(e));
memset(in,0,sizeof(in));
for(int i=0;i<n;++i){
if(c(p))return false;
int c=s[i]-'a';
for(int j=0;j<26;j++)if(c!=j&&ch(p,j)&&!e[c][j]){
e[c][j]=1;
++in[j];
}
p=ch(p,c);
}
topo();
for(int i=0;i<26;i++)if(in[i])return false;
return true;
}
给出一些回文串,将其两两组合,问新串为回文串的个数。回文串自己相加还是回文串,对于x的前置y,x和y拼接可能构成回文串,trie处理前缀,hash判断回文串,注意root和tot初始化为1,自己和自己拼了两次要减掉。
for(int i=1;i<=n;i++){
cin>>len[i]>>s[i];
t.insert(s[i],i);
has[i]=makehash(s[i],len[i]);
}
for(int i=1;i<=n;i++){
int p=1;
for(int j=0;j<len[i];j++){
p=t.t[p].ch[s[i][j]-'a'];
if(t.t[p].id&&(has[t.t[p].id]*pre[len[i]]%mod+has[i])%mod==(has[i]*pre[len[t.t[p].id]]%mod+has[t.t[p].id])%mod)ans+=t.t[p].cnt*2;
}
}
cout<<ans-n;
判断一个字符串是否出现过,若没有,求出与它距离为1的字符串的个数,1个距离指的是一次编辑操作,包括删除某个位置,插入某个字母,替换某个字母。给定单词插入trie,之后dfs,f代表是否编辑过,删除相当于直接跳到下一个字符继续找,插入相当于在p的孩子中查找到len-1的串,替换相当于在p的孩子中查找下一位到len-1的串。
void dfs(string s,int p,int l,bool f){
if(l==s.size()&&c(p)){
if(!f){
word=1;
return;
}
if(!vis[p])vis[p]=1,ans++;
return;
}
int c=s[l]-'a';
if(!f){
if(l<s.size())dfs(s,p,l+1,1);//删除
for(int i=0;i<26;i++){
if(ch(p,i)){
dfs(s,ch(p,i),l,1);//插入
if(i!=c)dfs(s,ch(p,i),l+1,1);//替换,需要去重
}
}
}
if(l>=s.size())return;
if(ch(p,c))dfs(s,ch(p,c),l+1,f);
}
}t;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
string s;
cin>>s;
t.insert(s);
}
for(int i=1;i<=m;i++){
string s;
cin>>s;
t.dfs(s,0,0,0);
if(word)cout<<"-1\n";
else cout<<ans<<'\n';
memset(vis,0,sizeof(vis));
word=ans=0;
}
return 0;
}
一台打印机有三种操作,末尾添加字母,末尾删除字母,打印当前单词,初始不含任何字母,要求打印一些单词的次数最少。每个单词都是要遍历一遍的,无法减少这个次数,考虑减少删除的次数,在打印完最后一个单词时可以直接输出并结束程序,所以将最长的单词放到最后去打印,这样可以减少尽量减少删除的次数,于是将最长单词进行标记,遍历trie时优先没有标记的节点。
inline void insert(string s){
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
if(!ch(p,c))ch(p,c)=++tot;
p=ch(p,c);
ans[p]=s[i];
}
c(p)++;
}
void maketag(string s){
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
p=ch(p,c);
flag[p]=1;
}
}
void solve(int p){
if(c(p)){
cnt++;
s+='P';
if(cnt==n){
cout<<s.size()<<'\n';
for(auto x:s)cout<<x<<'\n';
exit(0);
}
}
for(int i=0;i<26;i++){
if(!flag[ch(p,i)]&&ch(p,i)){
s+=ans[ch(p,i)];
solve(ch(p,i));
s+='-';
}
}
for(int i=0;i<26;i++){
if(flag[ch(p,i)]&&ch(p,i)){
s+=ans[ch(p,i)];
solve(ch(p,i));
s+='-';
}
}
}
}t;
对于不超过5个单词,求它们的最长公共子串的长度。最长公共子串肯定存在于第一个串中,于是将第一个串的所有后缀插入trie中,之后的每个串,用它们的后缀进行更新,被所有串覆盖过的子串则更新答案。
inline void insert(int l,int r,int id){
int p=root;
for(int i=l;i<=r;i++){
int to=0;
for(int j=h[p];j;j=e[j].next){
int y=e[j].to;
if(tk[y]==s[i]-'a'){
to=y;
break;
}
}
if(!to){
tk[++tot]=s[i]-'a';
add(p,tot);
to=tot;
}
d(to)=d(p)+1;
p=to;
l(p)|=1<<id;
if(l(p)==cmp)ans=max(ans,d(p));
}
}
inline void update(int l,int r,int id){
int p=root;
for(int i=l;i<=r;i++){
int to=0;
for(int j=h[p];j;j=e[j].next){
int y=e[j].to;
if(tk[y]==s[i]-'a'){
to=y;
break;
}
}
if(!to)return;
p=to;
l(p)|=1<<id;
if(l(p)==cmp)ans=max(ans,d(p));
}
}
}t;
for(int i=1;i<=n;i++)cmp|=1<<i;
for(int i=1;i<=len;i++)t.insert(i,len,1);
for(int i=2;i<=n;i++){
cin>>s+1;
len=strlen(s+1);
for(int j=1;j<=len;j++)t.update(j,len,i);
}
给出一些单词,之后询问一个单词s和一个数x,设s为t的前缀个数为a,若a>=x,输出第x个t,若x>a>0,输出第a个t,若a=0,输出404Error,每次输出t后将t的输出次数加1,排序按照输出次数从大到小为第一关键字,字典序为第二关键字。trie套平衡树,字典序大小的比较可以转化为trie树上的dfs序,为每个节点开一个平衡树,维护以root到当前节点的前缀中,出现的字符串。
void dfs(int p){
if(ipt[i(p)]==0){
ipt[i(p)]=++dfn;//将串的编号映射成dfs序
mp[dfn]=i(p);
}
for(int i=0;i<26;i++)if(ch(p,i))dfs(ch(p,i));
}
inline void build(string s,int id){//id是输入的字符串编号,不是节点对应的字符串编号
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
p=ch(p,c);
s(p)++;//前缀和
tr[p].insert({0,ipt[id]});//出现次数为第一关键字,字典序转化成的dfs序为第二关键字
}
}
inline void modify(string s,int id){
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'a';
p=ch(p,c);
auto it=tr[p].lower_bound({-use[id],ipt[id]});
tr[p].erase(it);
tr[p].insert({-use[id]-1,ipt[id]});
}
use[id]++;
}
}t;
for(int i=1;i<=n;i++)cin>>s[i],t.insert(s[i],i);
t.dfs(0);
for(int i=1;i<=n;i++)t.build(s[i],i);
int m;
cin>>m;
for(int i=1;i<=m;i++){
string a;
int k,p;
cin>>a>>k;
p=t.find(a);
if(p==-1){
cout<<"404Error\n";
continue;
}
k=min(k,t.t[p].sum);
auto it=tr[p].find_by_order(k-1);
int x=it->second;
cout<<s[mp[x]]<<'\n';
t.modify(s[mp[x]],mp[x]);
}
给出一些规则,可以添加一种规则或删除一种规则,一个IP地址对规则的匹配是规则中的最长前缀,问每个IP地址在某个时间段内匹配的变动次数。考虑只有一个IP地址的情况,询问时间在[l+1,r],可以转化成前缀和差分形式,对于添加规则,比原来和它匹配的规则更优时会影响,对于删除规则,删除已匹配的规则会影响。考虑修改一条规则对哪些IP地址有影响,若规则x是规则y的前缀,修改x不会影响对y匹配的IP地址,修改一个规则,只会影响以该点结尾,到下一个规则的结尾点的匹配的IP地址。将规则插入trie树,进行区间加,当一个点的end标记清空时才pushdown.
inline void add(int p,int x){
s(p)+=x;
a(p)+=x;
}
inline void pushdown(int p){
if(!l(p))l(p)=++tot;
if(!r(p))r(p)=++tot;
if(a(p)==0)return;
if(!e(l(p)))add(l(p),a(p));
if(!e(r(p)))add(r(p),a(p));
a(p)=0;
}
inline void modify(string s,int v){
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'0';
pushdown(p);
p=ch(p,c);
}
e(p)+=v;
add(p,1);
}
inline int query(string s){
int p=root,n=s.size();
for(int i=0;i<n;i++){
int c=s[i]-'0';
pushdown(p);
p=ch(p,c);
}
return s(p);
}
}t;
for(int i=1;i<=m;i++){
cin>>q[i].ip;
q[i].id=i;
int l,r;
cin>>l>>r;
del[l].push_back(i);
add[r].push_back(i);
}
for(int i=1;i<=n;i++){
t.modify(s[i],op[i]);
for(auto x:del[i])ans[q[x].id]-=t.query(q[x].ip);
for(auto x:add[i])ans[q[x].id]+=t.query(q[x].ip);
}