zoukankan      html  css  js  c++  java
  • [BZOJ4545]DQS的Trie(广义SAM+离线+树状数组)

    [BZOJ4545]DQS的Trie(广义SAM+离线+树状数组)

    题面

    一颗 Trie 树,q 次操作,操作有3种:
    1.求这棵树上本质不同的子串数量
    2.插入一个子树,保证总大小不超过 100000
    3.询问一个字符串在 Trie 树上出现过多少次,保证所有询问串总长度不超过 100000

    分析

    不考虑插入,操作1就是问所有节点的(len(x)-len(link(x)))之和。操作3就是字符串在SAM上匹配到的节点,节点的right集合大小,即parent树子树大小(去掉复制出来的节点)之和。
    SAM+LCT维护子树显然是可行的,但在考场上不一定能写出来。不妨把操作离线,把所有子树插入后的Trie树建出来,然后对它建广义后缀自动机。然后DFS整个parent树,记录DFS序,用来把子树和转化成区间和。容易发现虽然树的形态在变,但用最终树的DFS序来求和是等价的。于是可以用树状数组按照DFS序维护每个点的子树大小
    接着遍历操作,发现操作2实际上就是最终自动机的一些节点由未出现变成出现的过程。首先按照DFS序单点加1,表示增加了一个出现的节点。然后沿着link往上跳,把节点由未出现标记成出现,跳到已经出现的节点位置。一边跳一边累加当前节点的(len(x)-len(link(x)))之和。由于每个点只会被标记一次,总复杂度是(O(n))的。
    对于询问,1操作直接输出累加的和。3操作在SAM上匹配,然后找到匹配到的节点,DFS序区间查询子树大小。

    该离线做法不仅不容易写错,且常数比LCT小。

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #define maxn 500000
    #define maxc 26
    using namespace std;
    typedef long long ll;
    template<typename T> void qread(T &x) {
    	x=0;
    	T sign=1;
    	char c=getchar();
    	while(c<'0'||c>'9') {
    		if(c=='-') sign=-1;
    		c=getchar();
    	}
    	while(c>='0'&&c<='9') {
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	x=x*sign;
    }
    template<typename T> void qprint(T x) {
    	if(x<0) {
    		putchar('-');
    		qprint(-x);
    	} else if(x==0) {
    		putchar('0');
    		return;
    	} else {
    		if(x>=10) qprint(x/10);
    		putchar('0'+x%10);
    	}
    }
    
    int m;
    struct qtype {
    	int opt;
    	string qs;//离线记录询问串 
    	vector<int>subtr;//修改子树 
    } q[maxn+5];
    
    struct fenwick_tree {
    	int sz;
    	int c[maxn+5];
    	inline int lowbit(int x) {
    		return x&(-x);
    	}
    	void add(int x,int val) {
    		for(int i=x; i<=sz; i+=lowbit(i)) c[i]+=val;
    	}
    	int sum(int x) {
    		int ans=0;
    		for(int i=x; i>0; i-=lowbit(i)) ans+=c[i];
    		return ans;
    	}
    	int query(int l,int r) {
    		return sum(r)-sum(l-1);
    	}
    } B;
    
    struct SAM {
    #define len(x) (t[x].len)
    #define link(x) (t[x].link)
    	struct node {
    		int len;
    		int link;
    		int ch[maxc];
    	} t[maxn+5];
    	vector<int>E[maxn+5];
    	const int root=1;
    	int ptr=1;
    	int extend(int last,int c) {
    //		if(t[last].ch[c]&&len(last)+1==len(t[last].ch[c])) return t[last].ch[c];
    		int p=last,cur=++ptr;
    		len(cur)=len(last)+1;
    		while(p&&!t[p].ch[c]) {
    			t[p].ch[c]=cur;
    			p=link(p);
    		}
    //		bool flag=0;
    //		int clo;
    		if(p==0) link(cur)=root;
    		else {
    			int q=t[p].ch[c];
    			if(len(p)+1==len(q)) link(cur)=q;
    			else {
    //				if(p==last) flag=1;
    				int clo=++ptr;
    				link(clo)=link(q);
    				len(clo)=len(p)+1;
    				for(int i=0; i<maxc; i++) t[clo].ch[i]=t[q].ch[i];
    				link(q)=link(cur)=clo;
    				while(p&&t[p].ch[c]==q) {
    					t[p].ch[c]=clo;
    					p=link(p);
    				}
    			}
    		}
    		return cur;
    	}
    
    	int tim;
    	int dfnl[maxn+5],dfnr[maxn+5];
    	void get_dfn(int x,int f) {
    		dfnl[x]=++tim;
    		for(int i=0; i<(int)E[x].size(); i++) {
    			int y=E[x][i];
    			if(y!=f) get_dfn(y,x);
    		}
    		dfnr[x]=tim;
    	}
    
    	bool vis[maxn+5];
    	ll strcnt=0;//本质不同子串个数
    	void update(int x) {
    		for(int y=x; y&&!vis[y]; y=link(y)) { //计算对子串个数的贡献
    			vis[y]=1;
    			strcnt+=len(y)-len(link(y));
    		}
    		B.add(dfnl[x],1);
    	}
    	ll query1() {
    		return strcnt;
    	}
    	int query3(string &s) {
    		int x=root;
    		for(int i=0; i<(int)s.length(); i++) {
    			int c=s[i]-'a';
    			if(!t[x].ch[c]) return 0;
    			x=t[x].ch[c];
    		}
    		return B.query(dfnl[x],dfnr[x]);
    	}
    	void build_fail() {
    		for(int i=2; i<=ptr; i++) E[link(i)].push_back(i);
    		get_dfn(1,0);
    		B.sz=tim;
    	}
    } T1;
    
    struct Trie {
    	vector<pair<int,int> >E[maxn+5];
    	void add_edge(int u,int v,int w) {
    		E[u].push_back(make_pair(v,w));
    		E[v].push_back(make_pair(u,w));
    	}
    
    	int pos[maxn+5];
    	void bfs(int id,int s) {//bfs建广义SAM 
    		static bool vis[maxn+5];
    		queue<int>qu;
    		qu.push(s);
    		while(!qu.empty()) {
    			int x=qu.front();
    			qu.pop();
    			vis[x]=1;
    			if(x!=s) q[id].subtr.push_back(x);//根节点之前存在,不用更新 
    			for(int i=0; i<(int)E[x].size(); i++) {
    				int y=E[x][i].first;
    				if(!vis[y]) {
    					pos[y]=T1.extend(pos[x],E[x][i].second);
    					qu.push(y);
    				}
    			}
    		}
    	}
    } T2;
    
    
    
    int main() {
    	int id,n0,u,v,rt,sz;
    	static char tmp[maxn+5];
    	qread(id);
    	qread(n0);
    	for(int i=1; i<n0; i++) {
    		qread(u);
    		qread(v);
    		scanf("%s",tmp);
    		T2.add_edge(u,v,tmp[0]-'a');
    	}
    	T2.pos[1]=1;
    	T2.bfs(0,1);
    
    	qread(m);
    	for(int i=1; i<=m; i++) {
    		qread(q[i].opt);
    		if(q[i].opt==2) {
    			qread(rt);
    			qread(sz);
    			for(int j=1; j<sz; j++) {
    				qread(u);
    				qread(v);
    				scanf("%s",tmp);
    				T2.add_edge(u,v,tmp[0]-'a');
    			}
    			T2.bfs(i,rt);
    		} else if(q[i].opt==3) {
    			scanf("%s",tmp);
    			q[i].qs=string(tmp);
    		}
    	}
    
    	T1.build_fail();
    	
    	for(int j=0; j<(int)q[0].subtr.size(); j++) {//更新初始答案 
    		int x=q[0].subtr[j];
    		T1.update(T2.pos[x]);
    	}
    	for(int i=1; i<=m; i++) {
    		if(q[i].opt==1) {
    			qprint(T1.query1());
    			putchar('
    ');
    		} else if(q[i].opt==2) {
    			for(int j=0; j<(int)q[i].subtr.size(); j++) {
    				int x=q[i].subtr[j];
    				T1.update(T2.pos[x]);
    			}
    		}else{
    			qprint(T1.query3(q[i].qs));
    			putchar('
    ');
    		}
    	}
    }
    
  • 相关阅读:
    用C#做成的程序如何打包部署,在其他PC机运行
    ZeroMQ——一个轻量级的消息通信组件 C#
    Google Protocol Buffers 快速入门(带生成C#源码的方法)
    【转】如何一直保持测试工作的热情
    【转】关于什么是测试专家的讨论
    释放对某端口的占用
    Android端手机测试体系
    【转】减少缺陷漏测的系统方法体系思考(10年经验的反思)
    【转】3种类型的测试专家之路选择
    【转】手机应用软件测试点汇总
  • 原文地址:https://www.cnblogs.com/birchtree/p/12628554.html
Copyright © 2011-2022 走看看