CF914E Palindromes in a Tree 题解
一个经典套路是,一个字符串如果重排后可以变成回文,那么其充要条件是出现次数为奇数的字符不超过
注意到题目的字符集是 a 到 t,这之间总共
然后就可以愉快点分治了。设
注意两条路径合并,分治重心是唯一一个贡献错误的点,需要重新计算。
// LUOGU_RID: 123808133
#pragma GCC optimize("-Ofast,fast-math,-inline")
#pragma GCC target("avx,sse,sse2,sse3,popcnt")
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
using namespace std;
#ifndef ONLINE_JUDGE
#define __builtin_popcount __popcnt
#endif // !ONLINE_JUDGE
const int N = 2e5 + 5, M = 2e6 + 5;
char c[N];
int n;
vector<int> G[N];
class SegmentTree
{
public:
long long cf[N], ans[N];
void update(int l, int r, long long v)
{
cf[l] += v;
cf[r + 1] -= v;
}
void getans()
{
for (int i = 1; i <= n; i++) ans[i] = ans[i - 1] + cf[i];
}
};
class TreeCut
{
public:
int dep[N], fa[N], sz[N], son[N], top[N], id[N], idx;
SegmentTree sgt;
void dfs1(int u, int f)
{
dep[u] = dep[f] + 1;
fa[u] = f;
sz[u] = 1;
for (auto& j : G[u])
{
if (j ^ f)
{
dfs1(j, u);
sz[u] += sz[j];
if (sz[son[u]] < sz[j]) son[u] = j;
}
}
}
void dfs2(int u, int f)
{
top[u] = f;
id[u] = ++idx;
if (!son[u]) return;
dfs2(son[u], f);
for (auto& j : G[u])
{
if ((j ^ son[u]) && (j ^ fa[u])) dfs2(j, j);
}
}
void build()
{
dfs1(1, 1);
dfs2(1, 1);
//sgt.build(1, 1, n);
}
void update(int u, int v, long long k)
{
//cout << "!!: " << u << " " << v << " " << k << "\n";
if (!k) return;
while (top[u] ^ top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
sgt.update(id[top[u]], id[u], k);
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
sgt.update(id[u], id[v], k);
}
}tc;
int p[N];
long long cnt[M];
int cur;
vector<int> v[N], vv[N];
int sz[N];
bool del[N];
int tot, wc;
void get_size(int u, int f)
{
sz[u] = 0;
if (del[u]) return;
sz[u] = 1;
for (auto& j : G[u])
{
if (j ^ f) get_size(j, u), sz[u] += sz[j];
}
}
void calc_wc(int u, int f)
{
if (del[u]) return;
int maxn = tot - sz[u];
for (auto& j : G[u])
{
if (j ^ f)
{
calc_wc(j, u);
maxn = max(maxn, sz[j]);
}
}
if (maxn <= (tot >> 1)) wc = u;
}
long long nc = 0;
void dfs(int u, int f)
{
if (del[u]) return;
p[u] = p[f] ^ (1 << (c[u] - 'a'));
cnt[p[u]]++;
v[cur].emplace_back(u);
if (v[cur].size() == 1) vv[cur].emplace_back(u);
for (auto& j : G[u])
{
if (j ^ f) dfs(j, u);
}
}
long long res[N];
void solve(int u)
{
if (del[u]) return;
get_size(u, 0);
nc = 0;
tot = sz[u];
cur = 0;
wc = 0;
calc_wc(u, 0);
u = wc;
del[u] = 1;
p[u] = (1 << (c[u] - 'a'));
//cnt[p[u]]++;
for (auto& j : G[u])
{
if (del[j]) continue;
cur++;
v[cur].clear();
vv[cur].clear();
dfs(j, u);
}
int ncur = 0;
for (auto& j : G[u])
{
if (del[j]) continue;
ncur++;
for (auto& j : v[ncur])
{
cnt[p[j]]--;
}
for (auto& j : v[ncur])
{
if (__builtin_popcount(p[j]) <= 1)
{
tc.update(u, j, 1LL);
}
for (int i = 0; i < 20; i++)
{
int g = (1 << i);
int k = (g ^ p[j]) ^ (1 << (c[u] - 'a'));
if (cnt[k] > 0)
{
nc += cnt[k];
tc.update(vv[ncur][0], j, cnt[k]);
}
}
int g = 0;
int k = (g ^ p[j]) ^ (1 << (c[u] - 'a'));
if (cnt[k] > 0)
{
nc += cnt[k];
tc.update(vv[ncur][0], j, cnt[k]);
}
}
for (auto& j : v[ncur])
{
cnt[p[j]]++;
}
}
for (int i = 1; i <= cur; i++)
{
for (auto& j : v[i]) cnt[p[j]] = 0;
}
tc.update(u, u, (nc >> 1LL));
for (auto& j : G[u]) solve(j);
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
G[u].emplace_back(v);
G[v].emplace_back(u);
}
for (int i = 1; i <= n; i++) cin >> c[i];
tc.build();
solve(1);
tc.sgt.getans();
for (int i = 1; i <= n; i++) cout << tc.sgt.ans[tc.id[i]] + 1LL << " ";
return 0;
}
分类:
题解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现