CF914E Palindromes in a Tree 题解

一个经典套路是,一个字符串如果重排后可以变成回文,那么其充要条件是出现次数为奇数的字符不超过 11 个。证明显然。

注意到题目的字符集是 a 到 t,这之间总共 2020 个字符。我们只关心一个字符的出现次数的奇偶性,而非具体值。也就是说,3355 本质等价。不妨考虑状态压缩,每一位储存这个字符出现次数取模 22 的结果。

然后就可以愉快点分治了。设 disudis_u 表示 uu 到分治中学的路径的上述状态压缩的结果。容易发现,两条路径拼接相当于两个值异或,对于 ab=ca \oplus b = c,那么 b=cab = c \oplus a。对于每个点,枚举 cc,容易知道 ccpopcount(c)1popcount(c) \leq 1 的所有数,可以约 2020 次暴力枚举。然后套上两点路径上每个点点权 +k+k 的做法,就可以做到 O(nlog2n)O(n \log^2 n)

注意两条路径合并,分治重心是唯一一个贡献错误的点,需要重新计算。

// 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;
}
posted @   HappyBobb  阅读(8)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示