CF1010F Tree
题意:
- 给定一棵根为 的二叉树 ,根上有 个水果。
- 某些枝条(二叉树的边)会断掉,留下一个包含根节点的联通块 。
- 给剩下的 中每个点 赋点权 表示这个点上的水果数量,满足 并且 。
- 计算合法二元组 的数量,对 取模。
题解:
考虑差分,,特别地, 是叶子时,。
显然有 ,并且序列 和 一一对应,考虑数合法 的个数。那么 相当于 的前缀和,则 ,相当于把 拆分成 个互相区分的数,方案数为 ,于是问题转换为给定 ,求包含 节点的大小为 的连通块 的个数。
不难想到树形 dp,令 表示 子树内包含 且大小为 的连通块个数,令 分别是 的左右儿子,转移是显然的背包:
这是卷积的形式,考虑写出 的 OGF:
转移可以写成如下形式:
当 不存在时,视作 。于是我们就有了一个 的暴力 NTT 卷积做法。
考虑类似动态 dp,我们只关心 的值,所以考虑只维护出所有重链顶端的点 的 。
假设一条重链从链顶到链底形如 。考虑 的重儿子为 ,轻儿子为 ,显然轻儿子是另一条重链的链顶,它的 可以递归地进行维护,所以可以假设我们现在知道了所有 ,根据刚才的转移:
- 由于 为叶子节点,。
- 。
这就满足很好的序列递推性质:
接下来的事情就很简单了,我们发现这个东西可以分治乘来维护:
显然有:
维护二元组 即可分治 NTT 乘法,答案即为:
每个点往根走,只有经过轻边才会对复杂度贡献,所以一个点至多贡献 次,加上分治乘法复杂度是 的,但是众所周知,树剖卡不满。
很好写。
// Problem: F. Tree
// Contest: Codeforces - Codeforces Round 499 (Div. 1)
// URL: https://codeforces.com/problemset/problem/1010/F
// Memory Limit: 256 MB
// Time Limit: 7000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
namespace vbzIO {
char ibuf[(1 << 20) + 1], *iS, *iT;
#if ONLINE_JUDGE
#define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
#else
#define gh() getchar()
#endif
#define mt make_tuple
#define mp make_pair
#define fi first
#define se second
#define pc putchar
#define pb emplace_back
#define ins insert
#define era erase
typedef tuple<int, int, int> tu3;
typedef pair<int, int> pi;
inline int rd() {
char ch = gh();
int x = 0;
bool t = 0;
while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gh();
return t ? ~(x - 1) : x;
}
inline ll rdl() {
char ch = gh();
ll x = 0;
bool t = 0;
while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gh();
return t ? ~(x - 1) : x;
}
inline void wr(int x) {
if (x < 0) x = ~(x - 1), pc('-');
if (x > 9) wr(x / 10);
pc(x % 10 + '0');
}
}
using namespace vbzIO;
typedef vector<int> poly;
const int N = 1e5 + 100;
const int M = 3e5 + 300;
const int P = 998244353;
const int G = 114514;
poly dp[N];
vector<poly> p;
vector<int> g[N], cn[N];
int n, m, inv[N], sz[N], fa[N], son[N], ch[N][2];
int Add(int x, int y) { return x += y, (x >= P) ? (x - P) : x; }
int Mul(int x, int y) { return 1ll * x * y % P; }
int Sub(int x, int y) { return Add(x, P - y); }
void Addi(int &x, int y) { x = Add(x, y); }
void Muli(int &x, int y) { x = Mul(x, y); }
void Subi(int &x, int y) { x = Sub(x, y); }
int qpow(int p, int q) {
int res = 1;
for (; q; q >>= 1, p = 1ll * p * p % P)
if (q & 1) res = 1ll * res * p % P;
return res;
}
const int iG = qpow(G, P - 2);
poly Plus(poly x, int y) {
if (!x.size()) x.resize(1);
return Addi(x[0], y), x;
}
poly Plus(poly x, poly y) {
int szx = x.size(), szy = y.size();
if (szx < szy) swap(x, y);
for (int i = 0; i < min(szx, szy); i++) Addi(x[i], y[i]);
return x;
}
void NTT(int *f, int len, int lim, int op) {
static int tr[M];
for (int i = 1; i < len; i++)
tr[i] = (tr[i >> 1] >> 1) | ((i & 1) << (lim - 1));
for (int i = 0; i < len; i++)
if (i < tr[i]) swap(f[i], f[tr[i]]);
for (int o = 2, k = 1; k < len; o <<= 1, k <<= 1) {
int tg = qpow(~op ? G : iG, (P - 1) / o);
for (int i = 0; i < len; i += o) {
for (int j = 0, w = 1; j < k; j++, Muli(w, tg)) {
int x = f[i + j], y = Mul(f[i + j + k], w);
f[i + j] = Add(x, y);
f[i + j + k] = Sub(x, y);
}
}
}
if (~op) return;
int iv = qpow(len, P - 2);
for (int i = 0; i < len; i++) Muli(f[i], iv);
}
poly Conv(poly x, poly y) {
static int tx[M], ty[M];
int len = 1, lim = 0, szx = x.size(), szy = y.size();
while (len < szx + szy) len <<= 1, lim++;
for (int i = 0; i < len; i++) tx[i] = ty[i] = 0;
for (int i = 0; i < szx; i++) tx[i] = x[i];
for (int i = 0; i < szy; i++) ty[i] = y[i];
NTT(tx, len, lim, 1), NTT(ty, len, lim, 1);
for (int i = 0; i < len; i++) Muli(tx[i], ty[i]);
NTT(tx, len, lim, -1);
poly res;
for (int i = 0; i < szx + szy - 1; i++) res.pb(tx[i]);
return res;
}
pair<poly, poly> conq(int l, int r) {
if (l == r) return mp(Plus(p[l], 1), p[l]);
int mid = (l + r) >> 1;
auto lhs = conq(l, mid), rhs = conq(mid + 1, r);
return mp(Plus(Conv(Plus(rhs.fi, P - 1), lhs.se), lhs.fi), Conv(lhs.se, rhs.se));
}
void init(int lim) {
inv[1] = inv[0] = 1;
for (int i = 2; i <= lim; i++)
inv[i] = Mul(inv[P % i], P - P / i);
}
int hvy(int x) { return son[x] == ch[x][1]; }
int lht(int x) { return son[x] != ch[x][1]; }
#define lh(x) ch[x][lht(x)]
void dfs1(int u, int fat) {
fa[u] = fat, sz[u] = 1;
int ct = 0;
for (int v : g[u]) {
if (v == fat) continue;
dfs1(v, u), sz[u] += sz[v], ch[u][ct++] = v;
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int pr) {
cn[pr].pb(u);
if (son[u]) dfs2(son[u], pr);
if (lh(u)) dfs2(lh(u), lh(u));
}
void dfs3(int u) {
if (!son[u]) return dp[u].resize(2), dp[u][0] = dp[u][1] = 1, void();
for (int v : cn[u])
if (lh(v)) dfs3(lh(v));
p.clear();
for (int v : cn[u]) {
int fl = 0;
if (lh(v)) dp[lh(v)].ins(dp[lh(v)].begin(), 0), p.pb(dp[lh(v)]), fl = 1;
if (!fl) { poly tp; tp.resize(2), tp[1] = 1, p.pb(tp); }
}
dp[u] = conq(0, p.size() - 1).fi;
}
int main() {
n = rd(), m = rdl() % P, init(n);
for (int i = 1, u, v; i < n; i++)
u = rd(), v = rd(), g[u].pb(v), g[v].pb(u);
dfs1(1, 0), dfs2(1, 1), dfs3(1);
int ans = 0;
for (int i = 1, fc = 1; i < (int)dp[1].size(); i++)
Addi(ans, Mul(fc, dp[1][i])), Muli(fc, Add(m, i)), Muli(fc, inv[i]);
wr(ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?