• yww 与树上的回文串 (点分治+AC自动机+分块决策)


    yww 与树上的回文串

    官方题解不是人啊,半天看不懂

    首先是前置知识 Border定理,在这道题的应用就是把一个串的回文的前缀,按照长度分成\(O(\log |S|)\)个等差数列

    分析

    路径问题很常见的可以考虑点分治,问题在于如何合并两个子树里的串回文的个数

    如果符合条件的串只有在一边,可以直接\(dfs\),\(\text{hash}\)判断

    设符合条件的串被当前点分治的根分成了两部分\(S_x\),\(S_y\),且\(|S_x|\ge |S_y|\)

    其中\(|S_x|=|S_y|\)的部分可以特判一下

    可以认为,合法的情况就是\(S_x\)长度为\(|S_x|-|S_y|\)的前缀自身回文,剩余的部分与\(S_y\)相同

    那么我们枚举每个\(S_x\),建立AC自动机,用\(\text{hash}\)同步维护每个前缀是否回文,插入等差数列中,得到\(log|S|\)个等差数列

    接下来对于公差\(d\)我们分段决策

    每一段等差数列可以表示为三元组\((l,r,d)\),表示剩余的部分长度\(x\)的集合是\(x\in[l,r],x \equiv l(\mod d)\)

    而剩余的部分是当前串在\(fail\)树上的祖先,所以问题可以转化为一个多条件的树上前缀查询

    设常数\(S\)为决策边界

    如果\(d\leq S\),我们把每一个\(d\)的答案都统计下来,建立\(S^2\)棵虚空线段树,就能查询区间\([l,r]\)的答案了

    树上前缀查询可以离线询问,记录\(\text{trie}\)树上每个点作为\(|S_y|\)结尾的个数,枚举\(S\)种不同的模数更新,遍历\(fail\)树就能得到

    设当前点分治包含点个数为\(n\),这一部分复杂度是\(O(nS\log n)\)

    如果\(d>S\),直接用一个长度为\(n\)的数组记录树上前缀中长度为\(i\)的答案,枚举集合\((l,r,d)\)即可

    这一部分复杂度是\(O(\frac{n^2}{S})\)

    实测\(S=4,5\)时效果奇好。。

    const int N=5e4+10,SN=240;
    const ll K1=234234,K2=133123,P1=1e9+13,P2=19260817;
    
    int n;
    vector <pair <int,int> > G[N];
    vector <int> E[N];
    ll Pow1[N],Pow2[N];
    int trie[N][2],End[N],cnt,fail[N],dep[N]; // End数组记录当前节点作为Sy结尾的个数
    struct Node{ int l,r,d; };
    vector <Node> Q[N];
    void Clear(){
    	rep(i,0,cnt) fail[i]=trie[i][0]=trie[i][1]=End[i]=0,Q[i].clear(),E[i].clear();
    	cnt=0;
    }
    void Build(){
    	static queue <int> que;
    	rep(i,0,1) if(trie[0][i]) que.push(trie[0][i]),dep[trie[0][i]]=1;
    	while(!que.empty()) {
    		int u=que.front(); que.pop();
    		E[fail[u]].pb(u);
    		rep(i,0,1) {
    			int &v=trie[u][i];
    			if(!v) v=trie[fail[u]][i];
    			else dep[v]=dep[u]+1,fail[v]=trie[fail[u]][i],que.push(v);
    		}
    	}
    }
    
    int vis[N],mi,Rt,sz[N];
    void FindRt(int n,int u,int f) {
    	int ma=n-sz[u];
    	for(auto t:G[u]) if(!vis[t.first] && t.first!=f) FindRt(n,t.first,u),cmax(ma,sz[t.first]);
    	if(mi>ma) mi=ma,Rt=u;
    }
    
    int Maxlen,M;
    ll ans;
    int a[30][N],ac; //Palindrome Prefix 
    
    void pre_dfs(int u,int f,int d) {
    	cmax(Maxlen,d+2),sz[u]=1;
    	for(auto t:G[u]) if(!vis[t.first] && t.first!=f){
    		int v=t.first;
    		pre_dfs(v,u,d+1);
    		sz[u]+=sz[v];
    	}
    }
    
    void dfs(int u,int f,int now,int d,ll h1,ll h2,ll rh1,ll rh2,int k) {
    	if(now) End[now]++;
    	if(now && ac) 
            rep(i,1,ac) 
            	Q[now].pb((Node){d-a[i][a[i][0]],d-a[i][1],(a[i][0]>1?a[i][2]-a[i][1]:1)});
    	if(now && h1==rh1 && h2==rh2) {
    		ans+=k;
    		if(a[ac][0]==1 || a[ac][2]-a[ac][1]==d-a[ac][a[ac][0]]) a[ac][++a[ac][0]]=d;
    		else ++ac,a[ac][a[ac][0]=1]=d;
    	} //同步维护等差数列
    	for(auto t:G[u]) if(!vis[t.first] && t.first!=f){
    		int v=t.first,w=t.second;
    		if(!trie[now][w]) trie[now][w]=++cnt;
    		dfs(v,u,trie[now][w],d+1,(h1*K1+w)%P1,(h2*K2+w)%P2,(rh1+Pow1[d]*w)%P1,(rh2+Pow2[d]*w)%P2,k);
    	}
    	if(now && h1==rh1 && h2==rh2) if(--a[ac][0]==0) --ac; // 回溯等差数列
    }
    
    const int K=30;
    struct I_Hate_It{
    	int ls[N*K],rs[N*K],s[N*K],cnt;
    	void Upd(int &p,int l,int r,int x,int y) {
    		if(!p) p=++cnt,ls[cnt]=rs[cnt]=s[cnt]=0;
    		s[p]+=y;
    		if(l==r) return;
    		int mid=(l+r)>>1;
    		x<=mid?Upd(ls[p],l,mid,x,y):Upd(rs[p],mid+1,r,x,y);
    	}
    	int Que(int p,int l,int r,int ql,int qr) {
    		if(!p || (ql<=l && r<=qr)) return s[p];
    		int mid=(l+r)>>1,res=0;
    		if(ql<=mid) res+=Que(ls[p],l,mid,ql,qr);
    		if(qr>mid) res+=Que(rs[p],mid+1,r,ql,qr);
    		return res;
    	}
    } Tree;
    int rt[SN][SN],c[N];
    
    void dfs_getans(int u,ll &ans){ // 回答询问,注意要回溯
    	c[dep[u]]+=End[u];
    	if(u && End[u]) rep(i,1,M) Tree.Upd(rt[i][dep[u]%i],0,Maxlen,dep[u],End[u]);
    	for(Node t:Q[u]) if(t.d<=M) ans+=Tree.Que(rt[t.d][t.l%t.d],0,Maxlen,t.l,t.r);
    		else for(int i=t.l;i<=t.r;i+=t.d) ans+=c[i];
    	for(int v:E[u]) dfs_getans(v,ans);
    	c[dep[u]]-=End[u];
    	if(u && End[u]) rep(i,1,M) Tree.Upd(rt[i][dep[u]%i],0,Maxlen,dep[u],-End[u]);
    }
    
    ll Calc(){
    	cmax(Maxlen,10);
    	ll ans=0;
    	rep(i,1,cnt) ans+=1ll*End[i]*(End[i]-1)/2;
    	dfs_getans(0,ans);
    	return ans;
    }
    
    void Divide(int u){
    	vis[u]=1;
    	Maxlen=10;
    	pre_dfs(u,0,0),M=min(sqrt(Maxlen/10),4.0);
    	if(sz[u]==1) return;
    	for(auto t:G[u]) if(!vis[t.first]){
    		int v=t.first,w=t.second;
    		if(!trie[0][w]) trie[0][w]=++cnt;
    		dfs(v,u,trie[0][w],1,w,w,w,w,0),Build();
    		ll t=Calc();
    		ans-=t;
    		Clear();
    	}
    	dfs(u,0,0,0,0,0,0,0,1),Build();
    	ll t=Calc();
    	Clear();
    	ans+=t; Tree.cnt=0;
    	rep(i,0,M) rep(j,0,M) rt[i][j]=0;
    	for(auto t:G[u]) if(!vis[t.first]) {
    		int v=t.first;
    		mi=1e9,FindRt(sz[v],v,u);
    		Divide(Rt);
    	}
    }
    
    namespace ptChain{
    	int Check(){
    		rep(i,2,n-1) if(G[i].size()!=2) return 0;
    		return G[1].size()==1 && G[n].size()==1;
    	}
    	char s[N];
    	int cnt;
    	ll h1[N],h2[N];
    	ll rh1[N],rh2[N];
    	void dfs(int u,int f) {
    		for(auto t:G[u]) if(t.first!=f) {
    			s[++cnt]=t.second;
    			dfs(t.first,u);
    		}
    	}
    	int IsPal(int l,int r){
    		return 
    		(h1[r]-h1[l-1]*Pow1[r-l+1]%P1+P1)%P1==(rh1[l]-rh1[r+1]*Pow1[r-l+1]%P1+P1)%P1 && 
    		(h2[r]-h2[l-1]*Pow2[r-l+1]%P2+P2)%P2==(rh2[l]-rh2[r+1]*Pow2[r-l+1]%P2+P2)%P2;
    	}
    
    	void Solve() {
    		dfs(1,0);
    		rep(i,1,cnt) h1[i]=(h1[i-1]*K1+s[i])%P1;
    		rep(i,1,cnt) h2[i]=(h2[i-1]*K2+s[i])%P2;
    		drep(i,cnt,1) rh1[i]=(rh1[i+1]*K1+s[i])%P1;
    		drep(i,cnt,1) rh2[i]=(rh2[i+1]*K2+s[i])%P2;
    		ll ans=0;
    		rep(i,1,cnt) {
    			int l=0,r=min(i-1,cnt-i),res=-1;
    			while(l<=r) {
    				int mid=(l+r)>>1;
    				if(IsPal(i-mid,i+mid)) l=mid+1,res=mid;
    				else r=mid-1;
    			}
    			ans+=res+1;
    			if(i>1) {
    				l=0,r=min(i-2,cnt-i),res=-1;
    				while(l<=r) {
    					int mid=(l+r)>>1;
    					if(IsPal(i-1-mid,i+mid)) l=mid+1,res=mid;
    					else r=mid-1;
    				}
    				ans+=res+1;
    			}
    		}
    		printf("%lld\n",ans);
    	}
    }
    
    int main(){
    	freopen("string.in","r",stdin),freopen("string.out","w",stdout);
    	n=rd();
    	rep(i,2,n){
    		int u=rd(),v=rd(),w=rd();
    		G[u].pb(make_pair(v,w));
    		G[v].pb(make_pair(u,w));
    	}
    	Pow1[0]=Pow2[0]=1;
    	rep(i,1,N-1) Pow1[i]=Pow1[i-1]*K1%P1;
    	rep(i,1,N-1) Pow2[i]=Pow2[i-1]*K2%P2;
    	if(ptChain::Check()) return ptChain::Solve(),0;
    	Divide(1);
    	printf("%lld\n",ans);
    }
    
  • 相关阅读:
    【转】Shell编程基础篇-上
    【转】inotify+rsync实现实时同步
    Spring
    jdk,jre,tommcat配置问题
    Java前后台开发
    前端组件学习(一)
    报表工具进阶(二)
    查询时异步刷新问题--用到了ajax
    学习jaspersoft/JasperReport
    利用SQLYog操作数据库mysql
  • 原文地址:https://www.cnblogs.com/chasedeath/p/12733044.html
Copyright © 2020-2023  润新知