题目描述:https://codeforces.ml/contest/914/problem/E
题目大意:
给出一棵树,每个点有一个字母权值[a,z],定义回文路径:这条路径上的权值,经过重排后,可以得到一个回文。
输出每个点,在多少条回文路径上
Note:一个点也算一条,所以每个点答案至少为1
题目思路
用到了刚学的算法:树上点分治。
考虑一条链在什么情况下满足条件:最多只有一个字母出现奇数次。
所以我们维护当前这个条件:用二进制状压每一个字母,如果当前状态为2的幂或者为0即合法
对于一个根节点来说,可以用合并的思想算一下,经过当前根节点的与不经过当前根节点的合法路径
因为需要还原路径。
所以处理出,子树路径的异或值。
然后每次处理当前子树时,就把当前子树的异或值全部减掉,如果存在一个答案,那么说明这个节点有一个答案。
由于合并的链最少也是以根为端点,所以路径上的点有递归的效应:3->4 如果4存在一个答案,那么3也要加4的答案。
所以说,对于每一颗子树而言,都用回溯算一下。
最后不要忘记根节点的贡献:经过根节点的条数/2+根节点为端点的条数
PS:会爆ll
Code:
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=1e17;
const int maxn=1e6+6;
const int mod=998244353;
const double eps=1e-3;
inline bool read(ll &num)
{char in;bool IsN=false;
in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
int sz[maxn],mx[maxn],rt=0,sum;
int vis[maxn],dfn=0;
ll t[maxn*5];
vector<int>v[maxn];
ll res[maxn];
char str[maxn];
int c[maxn];
bool f[maxn*5];
void getrt(int u,int fa){
mx[u]=0;sz[u]=1;
for(int e:v[u]){
if(e==fa||vis[e]) continue;
getrt(e,u);
sz[u]+=sz[e];
mx[u]=max(mx[u],sz[e]);
}
mx[u]=max(mx[u],sum-sz[u]);
if(mx[rt]>mx[u]) rt = u;
}
void getdis(int u,int fa,int s,int val){
if(u!=fa) t[s]+=val;
for(int e:v[u]){
if(e==fa||vis[e]) continue;
getdis(e,u,s^c[e],val);
}
}
ll tempy = 0,tempx = 0;
ll go(int u,int fa,int s,int val){
ll ans = t[s^val];
// printf("%d %d\n",u,s);
for(int i=0;i<20;i++){
int x = 1<<i;
ans+=t[s^x^val];
}
tempy+=ans;
int judgex = s^val;
//debug(judgex);
if(f[judgex]){
tempx++;
ans++;
}
for(int e:v[u]){
if(e==fa||vis[e]) continue;
ans+=go(e,u,s^c[e],val);
}
res[u]+=ans;
return ans;
}
void calc(int u){
// printf("--------------------%d\n",u);
getdis(u,u,0,1);
// for(int i=0;i<=4;i++)
// printf("%d : %d\n",i,t[i]);
tempy = tempx = 0;
for(int e:v[u]){
if(vis[e]) continue;
getdis(e,u,c[e],-1);
// printf("%d:\n",e);
// for(int i=0;i<=4;i++)
// printf("%d : %d\n",i,t[i]);
go(e,u,c[e],c[u]);
getdis(e,u,c[e],1);
}
// debug(tempx);
// debug(tempy);
res[u]+=tempy/2+tempx;
getdis(u,u,0,-1);
}
void Solve(int u){
// debug(u);
vis[u]=1;calc(u);
for(int e:v[u]){
if(vis[e]) continue;
sum = sz[e];mx[rt = 0] = n;
getrt(e,u);
Solve(rt);
}
}
int main(){
read(n);
for(int i=1;i<=n-1;i++){
int x,y;scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
f[0]=true;
for(int i=0;i<20;i++) f[1<<i]=true;
scanf("%s",str+1);
for(int i=1;i<=n;i++){
int x = str[i]-'a';
c[i] = 1<<x;
}
///restart is OK
sum = mx[rt=0]=n;
getrt(1,0);
getrt(rt,rt);
Solve(rt);
for(int i=1;i<=n;i++) printf("%lld ",res[i]+1);
return 0;
}
/**
**/
最近应该会更新树上点分治的题目比较频繁.补一下这个知识!
顺带的还有dsu on a tree与树剖