CF678E(状压DP + 期望)

4648. 【NOIP2016提高A组模拟7.17】锦标赛

Problem

N N N个人进行 N − 1 N-1 N1轮淘汰赛。已知 i i i战胜 j j j胜率为 a i , j a_{i,j} ai,j,求一个排列使得 1 1 1获胜概率最大.

N ≤ 18 N\le 18 N18

Solution

首先考虑一个暴力做法:如果已经知道了排列,怎么计算 1 1 1获胜的概率。

我们令 f k , i f_{k,i} fk,i表示在进行到第 k k k轮时, i i i为擂主的概率。

那么如果知道了排列顺序 d d d,则可以直接按照下面这个方式进行DP:

f[1][d[1]] = 1;
F(i, 2, n)
	F(j, 1, n)
		if (d[i] ^ j) {
			f[i][d[i]] += f[i - 1][j] * a[d[i]][j];
			f[i][j] = f[i - 1][j] * a[j][d[i]];
		}

这样子会发现 n = 10 n=10 n=10时刚好被卡,然后这时有一个显然的结论:

1如果想要赢得概率最大,必须要最后一个出场

证明是几乎显然的,我们可以很容易的通过调整法反证。

于是 搜索 + 简单的DP 可以得到30分的不错分数。

并且通过这个性质,我们容易想到一个贪心的做法:

通过赢 1 1 1概率越大的放的越前,并且在此之后对排列进行调整,具体是每次枚举两个人,看如果交换它们后, 1 1 1赢得概率的是否变大,如果变大就进行调整。这样进行若干次之后,必定某一时刻会达到最优排列。

这种 调整 + 贪心 的思想可以拿到100分的不错分数,并且时间复杂度十分优秀。

#include  

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)

const int N = 20;

using namespace std;

int n, d[N]; bool vis[N];
double a[N][N], Ans, f[N][N];

double Doit() {
	mem(f, 0), f[1][d[1]] = 1;
	F(i, 2, n)
		F(j, 1, n)
			if (d[i] ^ j) {
				f[i][d[i]] += f[i - 1][j] * a[d[i]][j];
				f[i][j] = f[i - 1][j] * a[j][d[i]];
			}
	return f[n][1];
}

int main() {	
	scanf("%d", &n);
	F(i, 1, n)
		F(j, 1, n)
			scanf("%lf", &a[i][j]);
	
	F(i, 1, n - 1)
		d[i] = i + 1;
	d[n] = 1;

	F(k, 1, n)
		F(i, 1, n - 2)
			F(j, i + 1, n - 1) {
				Ans = Doit();
				swap(d[i], d[j]);
				double nw = Doit();
				if (nw < Ans)
					swap(d[i], d[j]);
			}

	printf("%.7lf\n", Doit());
}

事实上,这题显然是一道状压DP的题目。

但我们发现无论怎么样状压,正着推需要进行取max累加两种操作,显然是不行的。

那么尝试着从最终的结果往回推,发现问题就变得简单了许多。

因为任何一个局面(我们用状态 S S S代替),最终的结果一定都是 1 1 1赢,那不妨枚举一下当前这个局面下谁第一个出场,以及第一个出场的人与谁进行pk,并转移到当前状态。

那么我们就让 f i , j f_{i,j} fi,j表示当前局面为 i i i的情况下, j j j第一个出场, 1 1 1最后赢的概率。

每次则枚举一个与 j j j进行决斗的人 k k k,然后通过这样的转移来进行:

f [ i ] [ j ] = m a x { f [ i − s h l [ j − 1 ] ] [ k ] ∗ a [ k ] [ j ] + f [ i − s h l [ k − 1 ] ] [ j ] ∗ a [ j ] [ k ] } f[i][j] = max\{ f[i - shl[j - 1]][k] * a[k][j] + f[i - shl[k - 1]][j] * a[j][k]\} f[i][j]=max{f[ishl[j1]][k]a[k][j]+f[ishl[k1]][j]a[j][k]}

#include 

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define mx(a, b) ((a) = max(a, b))
#define mem(a, b) memset(a, b, sizeof a)

const int N = 19, M = 262200;

using namespace std;

int n, d[N], shl[N];
double ans, a[N][N], f[M][N];

int main() {
	scanf("%d", &n);
	F(i, 1, n)
		F(j, 1, n)
			scanf("%lf", &a[i][j]);

	shl[0] = 1;
	F(i, 1, n)
		shl[i] = shl[i - 1] * 2;

	f[1][1] = 1;
	F(i, 1, shl[n] - 1) {
		mem(d, 0);
		for (int x = i; x ; d[++ d[0]] = x & 1, x >>= 1);
		F(j, 1, n)
			if (d[j])
				F(k, 1, n)
					if (d[k] && (k ^ j))
						mx(f[i][j], f[i - shl[j - 1]][k] * a[k][j] + f[i - shl[k - 1]][j] * a[j][k]);
	}

	F(i, 1, n)
		mx(ans, f[shl[n] - 1][i]);
	printf("%.7lf", ans);
}

你可能感兴趣的:(概率-期望,动态规划/DP)