【LOJ6240】仙人掌

该博客详细介绍了如何解决LOJ6240仙人掌问题,通过思路要点和数学期望计算每个点被选中时连通块操作次数,并在给定仙人掌为树的情况下简化概率计算。博主提出建立圆方树来优化算法,通过动态规划求解特定的E(xi),最后实现O(N^3)的时间复杂度解决方案。

【题目链接】

【思路要点】

  • x i x_i xi 表示点 i i i 被选中时,点 i i i 所在的连通块共被操作了多少次,则有
    E ( A n s ) = E ( ∑ i = 1 N x i ) = ∑ i = 1 N E ( x i ) E(Ans)=E(\sum_{i=1}^{N} x_i)=\sum_{i=1}^{N} E(x_i) E(Ans)=E(i=1Nxi)=i=1NE(xi)
  • 考虑计算 x i x_i xi ,记 P i , j P_{i,j} Pi,j 表示在点 j j j 被选中之前 i i i j j j 始终连通的概率,则有
    E ( x i ) = ∑ j = 1 N P i , j E(x_i)=\sum_{j=1}^{N}P_{i,j} E(xi)=j=1NPi,j
  • 若给定的仙人掌是一棵树,那么记 i i i j j j 路径上的点数为 d i s t i , j dist_{i,j} disti,j ,有 P i , j = 1 d i s t i , j P_{i,j}=\frac{1}{dist_{i,j}} Pi,j=disti,j1
  • 建立仙人掌的圆方树,那么 i i i j j j 路径上经过的圆点不能比 j j j 先被选中,且经过的每一个环由必经点分割而成的两部分不能均有点比 j j j 先被选中。
  • i i i j j j 路径上走过环的总点数为 c n t cnt cnt ;在这些点中选出一个大小为 x x x 的集合,使得删去集合中的点后 i i i j j j 依然连通的方案数为 d p x dp_x dpx 。考虑枚举 j j j 被选中之前先被选中的点数 k k k ,则有 P i , j = ∑ k = 0 c n t − 1 1 k + 1 × d p k ( c n t k ) P_{i,j}=\sum_{k=0}^{cnt-1}\frac{1}{k+1}\times\frac{dp_k}{\binom{cnt}{k}} Pi,j=k=0cnt1k+11×(kcnt)dpk
  • 在仙人掌的圆方树上进行上述 d p dp dp ,可以在 O ( N 2 ) O(N^2) O(N2) 的时间内得出特定的 E ( x i ) E(x_i) E(xi)
  • 时间复杂度 O ( N 3 ) O(N^3) O(N3)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
const int MAXP = 805;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, tot, ans, size[MAXN], rnk[MAXN][MAXN];
int timer, dfn[MAXN], low[MAXN], dp[MAXN][MAXN];
int binom[MAXN][MAXN], invb[MAXN][MAXN];
int top, Stack[MAXN], inv[MAXN];
vector <int> a[MAXN], t[MAXP];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
void work(int pos, int fa, int cnt) {
	if (pos <= n) {
		for (int i = 0; i <= cnt; i++)
			update(ans, 1ll * dp[pos][i] * invb[cnt + 1][i] % P * inv[cnt + 1 - i] % P);
		for (auto x : t[pos])
			if (x != fa) work(x, pos, cnt);
	} else {
		int now = pos - n;
		static int tmp[MAXN][MAXN];
		memcpy(tmp[1], dp[fa], sizeof(dp[fa]));
		for (int i = 2; i <= size[now]; i++) {
			memset(tmp[i], 0, sizeof(tmp[i]));
			for (int j = 0; j <= cnt + i; j++) {
				tmp[i][j] = tmp[i - 1][j];
				if (j) update(tmp[i][j], tmp[i - 1][j - 1]);
			}
		}
		for (auto x : t[pos])
			if (x != fa) {
				int dis = abs(rnk[now][fa] - rnk[now][x]);
				for (int i = 0; i <= cnt + size[now] - 1; i++) {
					dp[x][i] = tmp[dis][i] + tmp[size[now] - dis][i] - dp[fa][i];
					if (dp[x][i] >= P) dp[x][i] -= P;
					else if (dp[x][i] < 0) dp[x][i] += P;
				}
			}
		for (auto x : t[pos])
			if (x != fa) work(x, pos, cnt + size[now] - 1);
	}
}
void dfs(int pos, int fa) {
	Stack[++top] = pos;
	low[pos] = dfn[pos] = ++timer;
	for (auto x : a[pos])
		if (!dfn[x]) {
			dfs(x, pos);
			chkmin(low[pos], low[x]);
			if (low[x] == dfn[pos]) {
				int tmp = 0; tot++;
				while (tmp != x) {
					tmp = Stack[top--];
					t[tot + n].push_back(tmp);
					t[tmp].push_back(tot + n);
					rnk[tot][tmp] = ++size[tot];
				}
				t[tot + n].push_back(pos);
				t[pos].push_back(tot + n);
				rnk[tot][pos] = ++size[tot];
			}
		} else chkmin(low[pos], dfn[x]);
}
int main() {
	read(n), read(m);
	for (int i = 0; i <= n; i++) {
		inv[i] = power(i, P - 2);
		binom[i][0] = invb[i][0] = 1;
		for (int j = 1; j <= i; j++) {
			binom[i][j] = (binom[i - 1][j] + binom[i - 1][j - 1]) % P;
			invb[i][j] = power(binom[i][j], P - 2);
		}
	}
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(1, 0);
	for (int i = 1; i <= n; i++) {
		memset(dp, 0, sizeof(dp));
		dp[i][0] = 1, work(i, 0, 0);
	}
	writeln(ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值