题目描述:
一颗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