Codeforces Round #728 (Div. 2) D. Tree Array

一道概率题。题意不再赘述。讲一讲思路。

由于题目的图形为一棵树,而初始节点为随机的,所以第一步需要枚举根节点,由题意得每个点为根的概率为 1 n \frac{1}{n} n1

题目求逆序对的期望,稍作思考,此题如果考虑序列对结果的贡献会非常复杂,因而转换思路,考虑将考虑范围缩小,直接考虑任意两点对结果的影响,这就用到了数据结构课上所经常提到的指示器变量的思想,很多时候能够巧妙化简问题。两个点的贡献就是一个指示器变量,对于任意两点 x , y x,y x,y,不妨设 x < y x<y x<y
如果先访问 y y y,那么这对节点的贡献即为 1 1 1,否则为 0 0 0。所以要求期望,我们只需要求出先访问到y这个节点的概率。

题目提到的能访问道德节点概率均等这个条件实际上具有很强的误导性。实际上经过思考可以发现,如果只考虑 x , y x,y x,y两点的贡献,如果新加入的点不在 x , y x,y x,y的路径上,无法对 x , y x,y x,y之间的贡献造成任何影响。这是因为这是一颗树,只会从上到下访问,而又因为这个序列要访问完所有点,所以迟早会访问到 x , y x,y x,y L c a Lca Lca,再访问二者路径上的点,在这之前的所有的操作形成的序列都对 x , y x,y x,y的贡献没有影响。

那么就可以将 x x x y y y的这条路径给抽离出来单独考虑。由于我们需要求先到y的概率,即序列已经有了 y y y,但是却不含 x x x的概率。当序列访问到 L C A ( x , y ) LCA(x,y) LCA(x,y)时,由题意得向 x x x走和向 y y y走的概率相同,又只有新加入的点在 x , y x,y x,y的路径上才有贡献,所以从 L C A LCA LCA x x x方向或者往 y y y方向走的概率同为 1 2 \frac{1}{2} 21

这样我们就可以设计一个 d p dp dp d p [ i ] [ j ] dp[i][j] dp[i][j]表示向 x x x走了 i i i步,向 y y y走了 j j j步的概率
d p [ i ] [ j ] = 1 2 ( d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] ) dp[i][j]=\frac{1}{2}(dp[i-1][j] + dp[i][j-1]) dp[i][j]=21(dp[i1][j]+dp[i][j1])
这样,枚举任意两个点x,y(x<y)求出其lca,则序列先到y的概率即为 ∑ i = 0 d e e p [ x ] − d e e p [ l c a ] − 1 d p [ d e e p [ y ] − d e e p [ l c a ] ] [ i ] \sum_{i=0}^{deep[x] - deep[lca] - 1} dp[deep[y] - deep[lca]][i] i=0deep[x]deep[lca]1dp[deep[y]deep[lca]][i]
但是如果这样写,你就属于是输在了终点,因为x,y已经分别是路径的终点,所以这里还有一点小小的概率上的坑。正确的公式应该是:
1 2 ∑ i = 0 d e e p [ x ] − d e e p [ l c a ] − 1 d p [ d e e p [ y ] − d e e p [ l c a ] − 1 ] [ i ] \frac{1}{2} \sum_{i=0}^{deep[x] - deep[lca] - 1} dp[deep[y] - deep[lca] - 1][i] 21i=0deep[x]deep[lca]1dp[deep[y]deep[lca]1][i]
最后一步需要分隔开考虑,这样求和之后就大功告成了(当然别忘了一些细节,比如最后除以n和求逆元,逆元可以使用递推公式 I n v [ i ] = ( m o d − m o d / i ) ∗ I n v [ m o d % i ] Inv[i]=(mod - mod/i)*Inv[mod\%i] Inv[i]=(modmod/i)Inv[mod%i])

总的时间复杂度:枚举根节点 O ( n ) O(n) O(n),st预处理RMQ O ( n l o g n ) O(nlogn) O(nlogn),枚举任意两点算概率,可以预处理前缀和优化,复杂度为 O ( n 2 ) O(n^2) O(n2),总时间复杂度 O ( n 3 ) O(n^3) O(n3)

参考代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>

using namespace std;

const int Maxn = 1010;

int rmq[Maxn][30];

const int mod = 1e9 + 7;

int dfs_clock = 0;
int dfn[Maxn], deep[Maxn], First[Maxn];
int Begin[Maxn], Next[Maxn], e, to[Maxn];

int dp[Maxn][Maxn];
int sum[Maxn][Maxn];
int Inv[Maxn];

void get_inv(int n) {
	Inv[1] = 1;
	for (int i = 2; i <= n; ++i) {
		Inv[i] = 1ll * (mod - mod / i) * Inv[mod % i] % mod;
	}
}

void add(int x, int y) {
	to[++e] = y;
	Next[e] = Begin[x];
	Begin[x] = e;
}

void Dfs(int h, int father) {
	dfn[++ dfs_clock] = h;
	First[h] = dfs_clock;
	
	for (int i = Begin[h]; i; i = Next[i]) {
		int v = to[i];
		if (v == father) continue;
		deep[v] = deep[h] + 1;
		Dfs(v, h);
		dfn[++ dfs_clock] = h;
	}
}

void Rmq() {
	for (int i = 1; i <= dfs_clock; ++i) {
		rmq[i][0] = dfn[i];
	}
	
	for (int j = 1; j < 20; ++j) {
		for (int i = 1; i <= dfs_clock; ++i) {
			if (i + (1 << j) - 1 > dfs_clock) continue;
			rmq[i][j] = rmq[i][j-1];
			if (deep[rmq[i + (1 << j-1)][j-1]] < deep[rmq[i][j]]) {
				rmq[i][j] = rmq[i + (1 << j-1)][j-1];
			}
		}
	}
}

int Query(int x, int y) {
	x = First[x];
	y = First[y];
	if (x > y) swap(x, y);
	int Dist = y - x + 1;
	int Log = log(Dist) / log(2);
	if (deep[rmq[x][Log]] < deep[rmq[y - (1 << Log) + 1][Log]]) {
		return rmq[x][Log];
	}
	return rmq[y - (1 << Log) + 1][Log];
}

void Init(int n) {
	dfs_clock = 0;
	for (int i = 1; i <= n; ++i) {
		deep[i] = 0;
		First[i] = 0;
	}
	
	for (int i = 1; i <= n * 2; ++i) 
		for (int j = 0; j < 20; ++j) {
			rmq[i][j] = 0;
		}
	
}

void get_dp(int n) {
	dp[0][0] = 1;
	for (int i = 0; i <= n; ++i) {
		for (int j = 0; j <= n; ++j) {
			if (i - 1 >= 0) {
				(dp[i][j] += 1ll * dp[i-1][j] * Inv[2] % mod) %= mod;
			}
			if (j - 1 >= 0) {
				(dp[i][j] += 1ll * dp[i][j-1] * Inv[2] % mod) %= mod;
			}
		}
	}
	
	for (int i = 0; i <= n; ++i) {
		for (int j = 0; j <= n; ++j) {
			if (i == 0) sum[i][j] = dp[i][j];
			else sum[i][j] = (sum[i-1][j] + dp[i][j]) % mod;
		}
	}
}

void solve(int n) {
	get_inv(n);
	get_dp(n);
	int Ans = 0;
	for (int i = 1; i <= n; ++i) {
		Init(n);
		Dfs(i, 0);
		Rmq();
		for (int j = 1; j <= n; ++j) {
			for (int k = j + 1; k <= n; ++k) {
				int Lca = Query(j, k);
				int Left_dist = deep[j] - deep[Lca];
				int Right_dist = deep[k] - deep[Lca];
				
				if (Right_dist == 0) (Ans += 1) %= mod;
				else if (Left_dist == 0) {}
				else {
					(Ans += 1ll * sum[Left_dist-1][Right_dist-1] * Inv[2] % mod) %= mod;
				}
			}
		}
	}
	
	Ans = 1ll * Ans * Inv[n] % mod;
	printf ("%d\n", Ans);
}

int main() {
	
	int n;
	scanf ("%d", &n);
	for (int i = 1; i < n; ++i) {
		int x, y;
		scanf ("%d%d", &x, &y);
		add(x, y);
		add(y, x);
	}
	
	solve(n);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值