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);
	}
posted @ 2022-11-14 18:04  半步蒟蒻  阅读(69)  评论(0)    收藏  举报