4648. 【NOIP2016提高A组模拟7.17】锦标赛
N N N个人进行 N − 1 N-1 N−1轮淘汰赛。已知 i i i战胜 j j j胜率为 a i , j a_{i,j} ai,j,求一个排列使得 1 1 1获胜概率最大.
N ≤ 18 N\le 18 N≤18
首先考虑一个暴力做法:如果已经知道了排列,怎么计算 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[i−shl[j−1]][k]∗a[k][j]+f[i−shl[k−1]][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);
}