[题目] E. Palindromes in a Tree
[题意] 给定一棵树,每个点都有一个 a~t 的字符,一条路径回文定义为路径上的字符存在一个排列构成回文串,求经过每个点的回文路径数.n<=2*10^5.
[算法] 点分治
[题解] 状压 20 位的二进制表示一条路径的字符状态,点分治过程中维护扫描过的路径只须维护状态桶数组,t[i] 表示前面状态为 i 的路径条数.
合并:考虑当前状态为 j,要使合并的状态满足条件即 i^j=1<
统计每个点:对于当前要处理的重心 x,先遍历所有子树得到整个 t[] 数组,然后对每个子树先删除其在桶里的状态,然后扫一遍贡献子树中每个点,最后将子树的状态加回桶中.
这样可以做到每条路径都贡献到每个点,要特殊处理重心的贡献.
复杂度 O(n log n).
#include<cstdio>
#include<cctype>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=200010,maxN=2000010;
int tot,first[maxn],sz[maxn],vis[maxn],sum,root,a[maxn],u,v,n;
ll ans[maxn],t[maxN];
struct edge{int v,from;}e[maxn*2];
void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void getroot(int x,int fa){
sz[x]=1;
bool ok=1;
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa&&!vis[e[i].v]){
getroot(e[i].v,x);
sz[x]+=sz[e[i].v];
if(sz[e[i].v]>sum/2)ok=0;
}
if(ok&&sz[x]>=sum/2)root=x;
}
void dfs(int x,int fa,int p,int s){
t[s^=(1<<a[x])]+=p;
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa&&!vis[e[i].v])dfs(e[i].v,x,p,s);
}
ll calc(int x,int fa,int s){
s^=(1<<a[x]);ll num=t[s];
for(int i=0;i<20;i++)num+=t[s^(1<<i)];
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa&&!vis[e[i].v])num+=calc(e[i].v,x,s);
ans[x]+=num;
return num;
}
void solve(int x,int s){
vis[x]=1;
dfs(x,0,1,0);
ll num=t[0];
for(int i=0;i<20;i++)num+=t[1<<i];
for(int i=first[x];i;i=e[i].from)if(!vis[e[i].v]){
dfs(e[i].v,x,-1,1<<a[x]);
num+=calc(e[i].v,x,0);
dfs(e[i].v,x,1,1<<a[x]);
}
ans[x]+=num/2;
dfs(x,0,-1,0);
for(int i=first[x];i;i=e[i].from)if(!vis[e[i].v]){
if(sz[e[i].v]>sz[x])sum=s-sz[x];else sum=sz[e[i].v];
getroot(e[i].v,x);
solve(root,sum);
}
}
char s[maxn];
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
insert(u,v);insert(v,u);
}
scanf("%s",s+1);
for(int i=1;i<=n;i++)a[i]=s[i]-'a';
sum=n;
getroot(1,0);
solve(root,sum);
for(int i=1;i<=n;i++)printf("%lld ",ans[i]+1);
return 0;
}
View Code
来源: http://www.bubuko.com/infodetail-2471529.html