題目描述: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與樹剖