jzoj6407 【NOIP2019模拟11.05】小 D 与随机 (容斥计数)

39 篇文章 0 订阅
6 篇文章 0 订阅

题意

一棵树,现在随机分配一个排列,设一种方案中,到根没有比他大的点(好点)的个数是 c c c,那么这种方案有 K c K^c Kc的贡献。求贡献和。
n ≤ 5000 , K ≤ 1 0 9 n\leq 5000,K\leq10^9 n5000,K109

思路

  • 题解看不懂
  • ∑ K c = ∑ ( K + 1 − 1 ) c = ∑ ( K − 1 ) i × ( c i ) \sum K^c=\sum (K+1-1)^c=\sum(K-1)^i\times \binom{c}{i} Kc=(K+11)c=(K1)i×(ic)
  • 将k-1,就可以转化成“至少”的计数问题。
  • 问题现在变成了:分配排列,方案权值和。
  • 这个问题可以dp解决。考虑到当前点的取值范围依赖于子树内最小好点,设 f [ i ] [ j ] f[i][j] f[i][j]表示i的子树内,最小的好点的是第j小的所有方案权值和。
  • 转移方法是枚举两颗子树合并后,最小好点的位置,然后再讨论当前点是不是好点,与之前点的相对大小。
  • 观察发现可以前缀和优化,注意循环顺序与范围,不要退化了。
  • O ( n 2 ) O(n^2) O(n2)

题解做法:(以下内容为口胡)

  • 类似氪金手游那题。
  • 考虑钦定恰好选一些好点(黑点)之后,整个树要满足什么:
  • 每个点和距离它最近的黑点祖先之间有连边,如果是黑点连黑点则是向下连边,否则是向上连边。a到b的边意思是a<b.
  • 注意,根一定是黑点,所以整个仍然是一颗树。
  • 假如是外向树(所有边都是向儿子连),那么方案数是 n ! ∏ 1 / s z [ x ] n! \prod1/sz[x] n!1/sz[x],即每个点的1/sz的乘积再乘上n!.
  • 否则,将所有内向边容斥是否满足限制。即有两种选择
  1. 去掉这条边,系数是1
  2. 将这条边反向,系数是-1
  • 现在用一个dp模拟上述整个过程。设 F [ i ] [ j ] F[i][j] F[i][j]表示i的子树内,当前外向树的“大小”是j,的贡献和。先将子树做背包合并,然后再考虑当前点是黑点或白点。
  • 若是白点,那么直接更新到 f [ i ] [ j + 1 ] f[i][j+1] f[i][j+1],系数1。
  • 若是黑点,则更新到 f [ i ] [ 0 ] f[i][0] f[i][0],系数K/(j+1)或者更新到 f [ i ] [ j + 1 ] f[i][j+1] f[i][j+1],系数-K/(j+1).
  • 最终的答案是 n ! ∑ F [ 1 ] [ i ] n!\sum F[1][i] n!F[1][i]
  • 也是 O ( n 2 ) O(n^2) O(n2),常数小。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5050, mo = 998244353;
ll n, k;
int final[N], nex[N * 2], to[N * 2], tot;
ll C[N][N];
ll f[N][N];
int sz[N];

void link(int x, int y) {
	to[++tot] = y, nex[tot] = final[x], final[x] = tot; 
}

ll sum[N];
void merge(ll *tmp, int x, int y) {
	memset(sum, 0, sizeof sum);
	for(int v = sz[y] + 1; v; v--) {
		sum[v] = (sum[v + 1] + f[y][v]) % mo;
	}
	for(int u = 1; u <= sz[x]; u++) {
		for(int e = u; e <= u + sz[y]; e++) {
			tmp[e] = (tmp[e] + f[x][u] * sum[e - u + 1] % mo 
					* C[e - 1][u - 1] % mo 
					* C[sz[x] + sz[y] - e][sz[x] - u]) % mo;
		}
	}
}

ll tmp[N];
void dfs(int x, int from) {
	int has = 0;
	for(int i = final[x]; i; i = nex[i]) {
		int y = to[i]; if (y != from) {
			dfs(y, x);
			if (has == 0) {
				memcpy(f[x], f[y], sizeof f[y]);
			} else {
				memset(tmp, 0, (5 + sz[x] + sz[y]) * 8);
				merge(tmp, x, y);
				merge(tmp, y, x);
				tmp[sz[x] + sz[y] + 1] = f[x][sz[x] + 1] * f[y][sz[y] + 1] % mo * C[sz[x] + sz[y]][sz[x]] % mo;
				memcpy(f[x], tmp, (5 + sz[x] + sz[y]) * 8);
			}
			sz[x] += sz[y];
			has = 1;
		}
	}
	sz[x] ++;
	if (!has) {
		f[x][1] = k - 1;
		f[x][2] = 1;
		return;
	}
	memset(tmp, 0, sizeof tmp);
	for(int i = 1; i <= sz[x]; i++) {
		tmp[1] = (tmp[1] + f[x][i] * (k - 1)) % mo;
		tmp[i + 1] = (tmp[i + 1] - f[x][i] * (k - 1)) % mo;
		tmp[i + 1] = (tmp[i + 1] + i * f[x][i]) % mo;//x不是好点
		tmp[i + 2] = (tmp[i + 2] - i * f[x][i]) % mo;
	}
	for(int i = 1; i <= sz[x] + 1; i ++) {
		tmp[i] = (tmp[i - 1] + tmp[i]) % mo;
		f[x][i] = tmp[i];
	}
}

int main() {
	freopen("random.in", "r", stdin);
	// freopen("random.out", "w", stdout);
	cin >> n >> k;
	C[0][0] = 1;
	for(int i = 1; i <= n; i++) {
		C[i][0] = 1;
		for(int j = 1; j <= i; j++) 
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mo;
	}
	for(int i = 1; i < n; i++) {
		int u, v; scanf("%d %d", &u, &v);
		link(u, v), link(v, u);
	}
	dfs(1, 0);
	ll ans = 0;
	for(int i = 1; i <= n + 1; i++) {
		ans = (ans + f[1][i]) % mo;
	}
	cout << (ans + mo) % mo << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值