题解:CF2101F Shoo Shatters the Sunshine
这道题是可以纯组合数学直接做的,只是分讨和容斥比较繁琐。
假设颜色给定,考虑蓝点构成的虚树,其直径的中点 \(k\),以 \(k\) 为根,将红点加入虚树,其余未加入的点为白点,答案为红点的最大深度加直径长度的一半,即红蓝点最大深度之和。
那么我们枚举中点,中点不一定为蓝点,随后枚举深度 \(d\),在深度为 \(d\) 的那一层点中会有两个不同分支的点被染成蓝色,深度大于 \(d\) 的点不能染蓝色,以内的点颜色任意。然后再枚举红点的最大深度 \(h\),深度大于 \(h\) 的点不能染红色,以内的点颜色任意,深度为 \(d,h\) 的点任意染色会有重复,容斥一下。
对于长度为奇数的直径,每条边也可做为中点,在每条边建一个虚点连接原边接的两点,随后按正常方式计算即可。
每层染色方案独立,答案为每层染色方案的乘积,考虑每层的方案数如何计算,分 \(9\) 类讨论:
| 深度范围 | 允许颜色 | 方案数 |
|---|---|---|
| \(i < \min(d,h)\) | 红蓝白 | \(3^{s_i}\) |
| \(d<h\) 时的 \(i=d\) | 红蓝白,蓝需来自不同分支 | \(A_i\) |
| \(d<h\) 时的 \(d<i<h\) | 红白 | \(2^{s_i}\) |
| \(d<h\) 时的 \(i=h\) | 红白,红至少一个 | \(2^{s_i}-1\) |
| \(d>h\) 时的 \(i=h\) | 红蓝白,红至少一个 | \(3^{s_i}-2^{s_i}\) |
| \(d>h\) 时的 \(h<i<d\) | 蓝白 | \(2^{s_i}\) |
| \(d>h\) 时的 \(i=d\) | 蓝白,蓝需来自不同分支 | \(B_i\) |
| \(d=h\) 时的 \(i=d\) | 红蓝白,蓝需来自不同分支且红至少一个 | \(A_i - B_i\) |
| \(i > \max(d,h)\) | 白 | \(1\) |
其中 \(s_i\) 表示第 \(i\) 层的节点数,\(c_{i,j}\) 表示第 \(j\) 条分支在第 \(i\) 层的节点数,接下来看 \(A_i\) 和 \(B_i\) 如何计算。
\(A_i\)
总数 \(3^{s_i}\) 减去不满足条件的情况:
- 蓝点 \(0\) 个:\(2^{s_i}\)
- 蓝点恰 \(1\) 个:\(s_i\times 2^{s_i-1}\)
- 蓝点 \(\ge 2\) 但全在分支 \(j\) 内:\((3^{c_{i,j}}-2^{c_{i,j}}-c_{i,j}2^{c_{i,j}-1})\times 2^{s_i-c_{i,j}}\)
\(B_i\)
总数 \(2^{s_i}\) 减去不满足条件的情况:
- 蓝点 \(0\):\(1\)
- 蓝点 \(1\):\(s_i\)
- 全在同一分支 \(j\) 且 \(\ge 2\):\(2^{c_{i,j}}-c_{i,j}-1\)
当然根也可以染色,分 \(7\) 类讨论:
| 深度范围 | 允许颜色 | 方案数 |
|---|---|---|
| 实点,\(d=h=0\) | 不合法 | \(0\) |
| 实点,\(d=0\) | 蓝 | \(1\) |
| 实点,\(h=0\) | 红 | \(1\) |
| 实点,\(d>0,h>0\) | 红蓝白 | \(3\) |
| 虚点,\(d=0\) | 不合法 | \(0\) |
| 虚点,\(h=0\) | 不合法 | \(0\) |
| 虚点,\(d>0,h>0\) | 白 | \(1\) |
dfs 预处理每个分支在每层的节点数与每层的节点总数,然后计算每层方案数与 \(d+h\) 的乘积就是当前根的答案。
直接这么做是 \(O(n^4)\) 的,但很明显可以优化。
设 \(f(i,d,h)\) 表示第 \(i\) 层按分讨计算出的方案数,\(g(u)\) 表示根 \(u\) 的染色方案数,以 \(u\) 为根的最大深度为 \(D\),那么答案为:
注意到每层的方案数的计算方式按类别可以分为 \(5\) 个部分,分别是:\(i<\min(d,h)\),\(i=d\) 或 \(i=h\),\(i\) 在 \(d,h\) 之间,\(i=d\) 或 \(i=h\),\(i>\max(d,h)\)。也就是 \(d,h\) 的位置把它们划分成了三个区间,其中后缀积恒为 \(1\),不用考虑,前缀是 \(3^{s_i}\) 的乘积,中间的区间是 \(2^{s_i}\) 的乘积,可以维护前缀积和乘法逆元加速计算。
全部预处理后就不用一个一个枚举每一层了,时间复杂度优化至 \(O(n^3)\),在随机树上快到飞起,但 CF 的数据是不会轻易让你通过的,当然继续优化也不难。
考虑再优化掉一层循环,分别处理 \(d<h,d>h,d=h\) 的情况。
我们预处理:
注意以上运算均在模意义下进行。
\(d<h\)
枚举 \(d\),\(h\) 的范围为 \(d<h\le D\),共 \(D-d\) 种配对,方案数为:
贡献为:
\(u\) 为实点,\(d=0\) 时 \(g(u)=1\),\(d>0\) 时 \(g(u)=3\);
\(u\) 为虚点,\(d=0\) 时 \(g(u)=0\),\(d>0\) 时 \(g(u)=1\)。
\(d>h\)
和上面的相同,枚举 \(h\),\(d\) 的范围为 \(h<d\le D\),共 \(D-h\) 种配对,方案数为:
贡献为:
\(u\) 为实点,\(h=0\) 时 \(g(u)=1\),\(h>0\) 时 \(g(u)=3\);
\(u\) 为虚点,\(h=0\) 时 \(g(u)=0\),\(h>0\) 时 \(g(u)=1\)。
\(d=h\)
这个可以直接枚举,方案数为:
贡献为:
\(u\) 为实点,\(i=0\) 时 \(g(u)=0\),\(i>0\) 时 \(g(u)=3\);
\(u\) 为虚点,\(i=0\) 时 \(g(u)=0\),\(i>0\) 时 \(g(u)=1\)。
注意由于我们添加了虚节点,\(d,h\) 的大小翻了一倍,实际答案为原来的一半,乘上一个 \(2\) 的逆元即可。
最后分析一下时间复杂度,设 \(m_i\) 表示分支数即 \(i\) 的度数,\(D_i\) 表示以 \(i\) 为根的最大深度,那么单根预处理的复杂度为 \(O(D_i\times m_i)\),计算贡献的复杂度为 \(O(D_i)\),总复杂度为 \(O(\sum D_i\times m_i)\),\(D_i\) 的量级是 \(O(n)\) 的,看作 \(n\) 提出去:\(O(n\sum m_i)\),由于树的总度数是 \(O(n)\) 级别的,因此时间复杂度均摊下来其实是 \(O(n^2)\),可以通过。
参考实现
#include<bits/stdc++.h>
#define cin_fast ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define fi first
#define se second
//#define int long long
#define in(a) a = read()
#define rep(i , a , b) for(int i = a ; i <= b ; i ++)
using namespace std;
typedef long long ll;
const int N = 6e3 + 5 , mod = 998244353;
const int inf = 0x3f3f3f3f;
const long long INF = 0x3f3f3f3f3f3f3f3f , inv_2 = 499122177;
inline int read() {
int x = 0;
char ch = getchar();
bool f = 0;
while('9' < ch || ch < '0') f |= ch == '-' , ch = getchar();
while('0' <= ch && ch <= '9') x = (x << 3) + (x << 1) + ch - '0' , ch = getchar();
return f ? -x : x;
}
struct Edge{
int n , v;
}edge[N << 1];
int head[N] , eid , dag[N];
void eadd(int u , int v) {
edge[++ eid].n = head[u] , edge[head[u] = eid].v = v;
edge[++ eid].n = head[v] , edge[head[v] = eid].v = u;
dag[u] ++ , dag[v] ++;
}
ll pw2[N] , pw3[N] , inv2[N] , pre2[N] , pre3[N] , prv[N] , pred[N] , preh[N] , F[N] , G[N];
int c[N][N] , s[N] , vt , m , D , n;
void gd(int u , int fa , int dep) {
if(u <= n) D = max(D , dep);
for(int i = head[u] ; i ; i = edge[i].n) {
int v = edge[i].v;
if(v == fa) continue;
gd(v , u , dep + 1);
}
}
void dfs(int u , int fa , int dep , int id) {
if(u <= n) c[dep][id] ++ , s[dep] ++;
for(int i = head[u] ; i ; i = edge[i].n) {
int v = edge[i].v;
if(v == fa) continue;
dfs(v , u , dep + 1 , id);
}
}
ll add(ll x , ll y) {
x += y;
if(x >= mod) x-= mod;
return x;
}
ll sub(ll x , ll y) {
x -= y;
if(x < 0) x += mod;
return x;
}
ll mul(ll x , ll y) {
return (x * y) % mod;
}
ll A(int i) {
if(s[i] == 0) return 0;
ll ans = sub(sub(pw3[s[i]] , pw2[s[i]]) , mul(s[i] , pw2[s[i] - 1])) , now = 0;
for(int j = 1 ; j <= m ; j ++) {
if(c[i][j] == 0) continue;
now = add(now , mul(sub(sub(pw3[c[i][j]] , pw2[c[i][j]]) , mul(c[i][j] , pw2[c[i][j] - 1])) , pw2[s[i] - c[i][j]]));
}
return sub(ans , now);
}
ll B(int i) {
if(s[i] == 0) return 0;
ll ans = add(pw2[s[i]] , m - 1) , now = 0;
for(int j = 1 ; j <= m ; j ++) now = add(now , pw2[c[i][j]]);
return sub(ans , now);
}
ll g(int u , int dep) {
if(u <= n) return dep == 0 ? 1 : 3;
else return dep == 0 ? 0 : 1;
}
ll a[N] , b[N];
signed main() {
//cin_fast;
pre2[0] = pre3[0] = prv[0] = pw2[0] = pw3[0] = inv2[0] = 1;
for(int i = 1 ; i < N ; i ++) {
pw2[i] = mul(pw2[i - 1] , 2);
pw3[i] = mul(pw3[i - 1] , 3);
inv2[i] = mul(inv2[i - 1] , inv_2);
}
int T;
in(T);
while(T --) {
eid = 0;
memset(head , 0 , sizeof(head)) , memset(dag , 0 , sizeof(dag));
ll ans = 0;
in(n);
int tot = n;
for(int i = 1 ; i < n ; i ++) {
int u , v;
in(u) , in(v);
eadd(u , ++ tot) , eadd(tot , v);
}
for(int u = 1 ; u <= tot ; u ++) {
m = dag[u] , D = 0;
gd(u , 0 , 0);
for(int i = 0 ; i <= D ; i ++) {
memset(c[i] , 0 , (m + 1) * sizeof(int));
s[i] = 0;
}
for(int i = head[u] , j = 1 ; i ; i = edge[i].n , j ++) {
int v = edge[i].v;
dfs(v , u , 1 , j);
}
for(int i = 1 ; i <= D ; i ++) {
a[i] = A(i) , b[i] = B(i);
pre2[i] = mul(pre2[i - 1] , pw2[s[i]]);
pre3[i] = mul(pre3[i - 1] , pw3[s[i]]);
prv[i] = mul(prv[i - 1] , inv2[s[i]]);
pred[i] = add(pred[i - 1] , mul(pre2[i - 1] , b[i]));
preh[i] = add(preh[i - 1] , mul(pre2[i - 1] , sub(pw2[s[i]] , 1)));
F[i] = add(F[i - 1] , mul(i , mul(pre2[i - 1] , b[i])));
G[i] = add(G[i - 1] , mul(i , mul(pre2[i - 1] , sub(pw2[s[i]] , 1))));
}
ans = add(ans , mul(g(u , 0) , G[D]));
for(int d = 1 ; d <= D ; d ++) {
ll now = add(sub(G[D] , G[d]) , mul(d , sub(preh[D] , preh[d])));
now = mul(now , mul(g(u , d) , mul(pre3[d - 1] , mul(a[d] , prv[d]))));
ans = add(ans , now);
}
ans = add(ans , mul(g(u , 0) , F[D]));
for(int h = 1 ; h <= D ; h ++) {
ll now = add(sub(F[D] , F[h]) , mul(h , sub(pred[D] , pred[h])));
now = mul(now , mul(g(u , h) , mul(pre3[h - 1] , mul(sub(pw3[s[h]] , pw2[s[h]]) , prv[h]))));
ans = add(ans , now);
}
for(int i = 1 ; i <= D ; i ++) {
ll now = 2 * i;
now = mul(now , mul(g(u , i) , mul(pre3[i - 1] , sub(a[i] , b[i]))));
ans = add(ans , now);
}
}
cout << mul(ans , inv_2) << '\n';
}
return 0;
}

浙公网安备 33010602011771号