[DFZ3]回文路径·解题报告

文章讨论了一种针对含有字母权重的树结构中寻找回文路径的问题。通过状态压缩和点分治策略,结合奇偶性判断,可以计算出每经过一个节点的回文路径数量。在算法实现中,利用桶存储奇偶情况,并通过线段树的思想更新答案,最终得出每个节点的贡献。
摘要由CSDN通过智能技术生成

题目描述:

        一颗n个结点的树,第i个结点的权值是一个字母c[i]。把从u点到v点的简单路径上的所有的点的权值收集起来形成一个序列,然后你可以对这个序列的字母进行任意的交换位置,如果通过最终可以得到一个回文字符串,那么这条简单路径就称为"回文路径"。注意u到v的简单路径等于v到u的简单路径,只计算一次。对于每个点i,计算并输出有多少条"回文路径"经过它。

输入格式

第一行,一个整数n。 2<=n<=200000.

接下来有n-1行,每行两个整数u和v,表示u和v有一条边。

最后一行,有n个字符,第i个字符是c[i]。 ‘a’<= c[i] <='t'。

输出格式

一行,n个整数。

输入/输出

输入:

5

1 2

2 3

3 4

3 5

abcbb

输出:

1 3 4 3 3 

思路:

阶段1

留意到“  ‘a’<= c[i] <='t' ” ,就是说最多20个字母;

且对于一条路径,上面所有不同的点出现个数为偶数或者只有一个为奇数就为回文路径,

所以可以状态压缩存储每条边中每个不同点的奇偶情况,且奇偶情况可以用桶来存,这是十分关键的。

不难想到,如果两条路径奇偶情况相同,或者只有一位不同(1<<x),就可以拼成回文路径,点分治大体是可以做的,并且偏向合并的写法(非容斥)。

阶段2

于是你意识到了题目要求出的是对于所有点,经过它的回文路径,而点分治只能求经过当前点,且在其分治子树下的回文路径,上面下来的求不了。

我们可以发现一个性质:

按照点分治以前的做法,新子树中的点,与之前的子树的信息进行合并后,对当前重心(Cent)产生贡献。

但是这道题中,合并而来的信息,对重心,与当前子树内所有的点,都能产生贡献。

具体而言,我们记2号点与之前子树(三角形)产生了t1条回文路径,

那么不仅Cent增加了t1条,1号点与2号点也同样增加了t1条回文路径

阶段3

看着这幅图,我联想到了线段树中的上传操作。我们不妨用tag数组记录当前点产生的回文路径,于是像遍历新子树一样,再次遍历每一颗子树,将每一个点的ans加上当前的tag与当前点的子树的tag。

接着开始思考细节问题。

1、首先,我们必须将合并子树(点分治传统操作)与更新子树ans的操作分开求。因为一个点的tag应该是对除了这个点所在子树之外的所有点拼接回文路径。也就是说我们需要用到的桶,要统计完所有奇偶情况。

2、注意上面是“对除了这个点所在子树之外的所有点”,所以,还需要在合并子树环节中对于每个tag先减去同一颗子树内的情况

于是,你怀着悲伤且绝望的心情打出了一份代码,然后AC了awawa

#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
int n,a[MAXN];
string st;

int head[MAXN],En;
int nxt[MAXN<<1],des[MAXN<<1];
inline void Add(int x,int y){
	nxt[++En]=head[x];
	head[x]=En;
	des[En]=y;
}


int DeepDark,Cent,siz[MAXN];
bool vis[MAXN];
void GetSize(int x,int f){
	siz[x]=1;
	for(int i=head[x];i;i=nxt[i]){
		int v=des[i];
		if(v==f||vis[v])continue;
		GetSize(v,x);
		siz[x]+=siz[v];
	}
}
void GetCenter(int x,int f,int SIZE){
	int depdak=0;
	for(int i=head[x];i;i=nxt[i]){
		int v=des[i];
		if(v==f||vis[v])continue;
		GetCenter(v,x,SIZE);
		if(siz[v]>depdak)
			depdak=siz[v];
	}
	if(SIZE-siz[x]>depdak)
		depdak=SIZE-siz[x];
	if(depdak<DeepDark){
		DeepDark=depdak;
		Cent=x;
	}
}
int bar[MAXN<<4],barr[MAXN<<4];
int dis[MAXN],dn,loc[MAXN];
long long ans[MAXN];
long long tag[MAXN];
void GetDistance(int x,int f,int d){
	dis[++dn]=d,loc[dn]=x;
	for(int i=head[x];i;i=nxt[i]){
		int v=des[i];
		if(v==f||vis[v])continue;
		GetDistance(v,x,d^(1<<a[v]));
	}
}
void GetAnswer(int x,int f){
	for(int i=head[x];i;i=nxt[i]){
		int v=des[i];
		if(v==f||vis[v])continue;
		GetAnswer(v,x);
		tag[x]+=tag[v];
	}
	ans[x]+=tag[x];
}
int L[MAXN],ln;
void Solve(){
	L[1]=1;
	int awa=(1<<a[Cent]);
	dn=ln=1;
	long long t1;
	dis[1]=awa;
	bar[awa]=1;
	for(int i=head[Cent];i;i=nxt[i]){
		int v=des[i];
		if(vis[v])continue;
		GetDistance(v,Cent,1<<a[v]);
		for(int j=L[ln]+1;j<=dn;++j){
			t1=0;
			for(int k=0;k<20;++k)
				t1+=1ll*bar[dis[j]^(1<<k)];
			t1+=1ll*bar[dis[j]];
			
			ans[Cent]+=t1;
		}
		///容斥↓ 
		for(int j=L[ln]+1;j<=dn;++j){
			++barr[awa^dis[j]];
		}
		for(int j=L[ln]+1;j<=dn;++j){
			t1=0;
			for(int k=0;k<20;++k)
				t1+=1ll*barr[dis[j]^(1<<k)];
			t1+=1ll*barr[dis[j]];
			tag[loc[j]]-=t1;
		}
		for(int j=L[ln]+1;j<=dn;++j){
			--barr[awa^dis[j]];
		}
		///容斥↑ 
		for(int j=L[ln]+1;j<=dn;++j){
			++bar[awa^dis[j]];
		}
		L[++ln]=dn;
	}
	ln=0;
	for(int i=head[Cent];i;i=nxt[i]){
		int v=des[i];
		if(vis[v])continue;
		for(int j=L[++ln]+1;j<=L[ln+1];++j){
			t1=0;
			for(int k=0;k<20;++k)
				t1+=1ll*bar[dis[j]^(1<<k)];
			t1+=1ll*bar[dis[j]];
			
			tag[loc[j]]+=t1;
		}
		GetAnswer(v,Cent);
	}
	
	for(int i=2;i<=dn;++i)
		bar[awa^dis[i]]=tag[loc[i]]=0;
	bar[awa]=tag[Cent]=0;
}
void DFZ(int x){
	GetSize(x,0);
	DeepDark=1919810;
	GetCenter(x,0,siz[x]);
	vis[Cent]=1;
	x=Cent;
	
	Solve();
	
	for(int i=head[Cent];i;i=nxt[i]){
		int v=des[i];
		if(vis[v])continue;
		DFZ(v);
		tag[x]+=tag[v];
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<n;++i){
		int t1,t2;
		cin>>t1>>t2;
		Add(t1,t2);
		Add(t2,t1);
	}
	cin>>st;
	for(int i=1;i<=n;++i){
		a[i]=st[i-1]-'a';
	}
	DFZ(1);
	for(int i=1;i<=n;++i){
		printf("%lld ",ans[i]+1);
	}
	return 0;
}

史山代码,谨慎参考qwq

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值