洛谷 P5405 [CTS2019] 氪金手游

题意:给定一个 \(n\) 个点的有向图,满足将有向边视为无向边后图为一棵树。每个点有点权 \(w_i\)。在 \(\{1,2,3,\dots,n\}\) 内随机选数,有 \(\frac{w_i}{\sum w_i}\) 的概率选到 \(i\)。可能重复抽到某数。当所有数都至少被抽到一次后,选数过程停止。记第一次选到 \(i\) 的时间为 \(t_i\),这一次选数过程被称为好的,当且仅当对于图中每一条有向边 \(u \to v\),满足 \(t_u < t_v\)。求选数过程是好的概率。
两种过程认为不同,当且仅当抽数次数不同,或某一次两种方案抽到的数不同。\(n\le 1000\)
更进一步地,\(w_i\) 不会直接给出,输入会给出 \(p_{i,j}\),表示 \(w_i\)\(p_{i,j}\) 的概率取 \(j\)。其中 \(1 \le j \le 3\)

先考虑 \(w_i\) 确定,且树为以 \(1\) 为根的外向树怎么做。设一个 \(f_u\) 表示 \(u\) 的子树内的概率。子树内的要求就是,\(u\) 一定要第一个被抽到,剩下的就递归成子问题。也就是,\(f_u=\frac{w_u}{sz_u} \times \prod_{v \in \text{son}_u} f_v\)。其中 \(sz_u\) 表示子树内每个点的 \(w_i\) 之和。

有人会说,子树之间看起来不是独立的,可能先抽了 \(u\) 中的又抽了另外一个子树里面的。这里的理解是,状态的定义是,只管 \(u\) 子树内的序的关系,其他关系满不满足任意。在此基础上可以理解为,抽子树外的卡片定义为【无效的】。概率会成比例地【浓缩】到子树内。然后就有 DP 式子。

这里由 DP 式子可以导出独立性,进而归纳地证明了 DP 的正确性。

个人认为上面这里的理解是最难的部分,/dk

进而思考外向森林:显然互不干扰,直接乘起来就行。

然后考虑容斥掉内向边。具体地,将一些内向边钦定为不满足,也就是强制变成外向边,剩余的变为【无所谓】,也就是不管这条边两边序的关系。
【无所谓】的意思好像就是分割成了森林哦。

进而有了带容斥系数的 DP:这里效仿 SAO,只需要记录 \(f_{u,i}\) 表示 \(u\) 为根的子树,连通块大小为 \(i\)带容斥系数的概率。然后转移就是看 \((u, v)\) 是外向边还是内向边。外向边就只能合并连通块,内向边分类,容斥成哪一种。转移是背包,时间复杂度 \(O(n^2)\)

代码好写。

#include <bits/stdc++.h>
using namespace std;

//#define filename "xxx" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
//#define multi_cases 1

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 1002;

const int P = 998244353;
void vadd(int &a, int b) { a += b; if(a >= P) a -= P; }

int inv[3000002];

int n, a[N][4], p[N][4];
vector<pii> G[N];

int f[N][N * 3], sz[N], coe = 1;
void DFS(int u, int fa) {
	sz[u] = 1;
	upw(i, 1, 3) f[u][i] = 1ll * p[u][i] * i % P;
	for(auto [v, w] : G[u]) if(v != fa) {
		DFS(v, u);
		vector<int> t(3 * (sz[u] + sz[v]) + 1);
		upw(i, 1, 3 * sz[u]) upw(j, 1, 3 * sz[v])
			vadd(t[i + j], 1ll * f[u][i] * f[v][j] % P);
		if(w) {
			coe = P - coe;
			upw(i, 1, 3 * sz[u]) upw(j, 1, 3 * sz[v])
				vadd(t[i], 1ll * f[u][i] * f[v][j] % P * (P-1) % P);
		}
		
		sz[u] += sz[v];
		upw(i, 1, 3 * sz[u]) f[u][i] = t[i];
	}
	upw(i, 1, 3 * sz[u]) f[u][i] = 1ll * f[u][i] * inv[i] % P;
}

void WaterM() {
	cin >> n;
	upw(i, 1, n) upw(j, 1, 3) cin >> a[i][j];
	upw(i, 1, n) {
		int sum = 0;
		upw(j, 1, 3) sum += a[i][j];
		upw(j, 1, 3) p[i][j] = 1ll * a[i][j] * inv[sum] % P;
	}
	upw(i, 1, n-1) {
		int u, v;
		cin >> u >> v;
		G[u].emplace_back(v, 0), G[v].emplace_back(u, 1);
	}
	DFS(1, 0);
	
	// upw(i, 1, n) {
		// upw(j, 1, 3 * sz[i]) cerr << f[i][j] << ' ';
		// cerr << '\n';
	// }
	
	int ans = 0;
	upw(i, 1, 3 * sz[1]) vadd(ans, f[1][i]);
	cout << 1ll * ans * coe % P << '\n';
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	
	inv[1] = 1;
	upw(i, 2, 3000000) inv[i] = 1ll * (P - P / i) * inv[P % i] % P;
	
	while(_--) WaterM();
	return 0;
}

posted @ 2025-09-07 09:14  Water_M  阅读(4)  评论(0)    收藏  举报