acm-(好题、神题)2020-2021 Winter Petrozavodsk Camp, Day 5 B.Lockout vs tourist

acm-(好题、神题)2020-2021 Winter Petrozavodsk Camp, Day 5 B.Lockout vs tourist_第1张图片
传送门
简要题意:你和tourist一起比赛做题,你们两个每轮同时决策做哪道题,如果选择相同的题目,那么你不得分,比赛继续进行,如果选择了不同的题目,那么你能拿下你选择的这道题的全部分数,比赛结束,tourist想让你得分最少,你想让得分最多,问在双方均采取最优决策的情况下你的期望得分。

这道题一看就非常难以下手,直接给出题解的神仙做法吧。首先tourist的决策一定是基于概率的,我们考虑给每个问题设置一个概率 p i p_i pi,代表 t o u r i s t tourist tourist p i p_i pi的概率在当前轮中选择问题 i i i,那么为了让你比分最小化,我们需要对于所有可能的 p p p数组产生的得分期望取最小值。而对于每个 p p p数组你则是想要让得分期望最大化,因此tourist必会考虑让你的最大化期望最小,对于固定的 p p p数组你的最大化期望是 max ⁡ i { a i ( 1 − p i ) + b i p i } \max_{i}\{a_i(1-p_i)+b_ip_i\} maxi{ai(1pi)+bipi},其中 b i b_i bi代表除去第 i i i道题后在剩下的题中进行最优博弈对应的得分期望(这样可以状压dp处理),这个式子的意思就是,假如你在当前轮选择做第 i i i道题目,那么你有 1 − p i 1-p_i 1pi的概率成功得到这个题目的分数,这时候比赛也就结束了,当然也有 p i p_i pi的概率跟tourist选择了同一道题从而不能拿下该题的分数,这时候的期望分数对应的是一个子问题的博弈结果,即在现有题目基础上除去第 i i i道题后对应的博弈结果。而tourist自然会取 min ⁡ p { max ⁡ i { a i ( 1 − p i ) + b i p i } } \min_p\{\max_{i}\{a_i(1-p_i)+b_ip_i\}\} minp{maxi{ai(1pi)+bipi}},这便是最终博弈对应的答案。

然后还需要观察到一个性质,在对 a a a数组从小到大排序之后,满足 ∀ i < j , a i ≤ a j , b i ≥ b j \forall ii<j,aiaj,bibj,这个很容易想明白,接下来我们考虑让上面那个式子的所有 p i p_i pi都为 0 0 0,我们现在有要找一个 p p p的分配方案,满足所有 p p p之和为 1 1 1并且 a i ( 1 − p i ) + b i p i a_i(1-p_i)+b_ip_i ai(1pi)+bipi的最大值最小。令 f i = a i ( 1 − p i ) + b i p i f_i=a_i(1-p_i)+b_ip_i fi=ai(1pi)+bipi,由于此时 p i = 0 p_i=0 pi=0,因此 f i = a i f_i=a_i fi=ai,函数单调不减,最大值在 f n f_n fn处取得,考虑增加 p n p_n pn看看会发生什么,如果 a n > b n a_n>b_n an>bn,增加 p n p_n pn会让 f n f_n fn减小,我们看看 f n f_n fn能否减小为 f n − 1 f_{n-1} fn1,如果能减少到,那么我们考虑继续分配 p p p p n − 1 p_{n-1} pn1 p n p_n pn,看 f n f_n fn f n − 1 f_{n-1} fn1能否减少到 f n − 2 f_{n-2} fn2…如果减少不到,那么把剩下可用的 p p p值全部分给 p n p_n pn即可。如果 a n < = b n a_n<=b_n an<=bn,那么不能分配 p p p p n p_n pn,而是分给其它 p i p_i pi,容易发现无论如何分配都满足 f i < = f n f_i<=f_n fi<=fn,因为 b i < = a n b_i<=a_n bi<=an。此外,再迭代处理的过程中,若发现有 a i < = b i a_i<=b_i ai<=bi,那么此时可以把剩下的 p p p全部分给前面的 p j ( j > i ) p_j(j>i) pj(j>i),这样 a i a_i ai就是全局能取到的最小值了。

说了这么多感觉还是非常乱,这道题就是非常神仙。 下面直接看看代码吧。

#include 
using namespace std;

const int maxn = 22;
typedef double db;
typedef long long ll;

int c[maxn],tot=0;
db a[maxn],b[maxn];
db dp[1<<maxn];

db solve(int n){
	db sumder=0,sump=0;
	if(a[1]<=b[1])return a[1];
	sumder=1./(a[1]-b[1]);
	for(int i=2;i<=n;++i){
		db deta=a[i-1]-a[i];
		db ad=deta*sumder;
		if(sump+ad>1)return a[i-1]-(1-sump)/sumder;
		if(a[i]<=b[i])return a[i];
		sumder+=1./(a[i]-b[i]);
		sump+=ad; 
	}
	return a[n]-(1-sump)/sumder;
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;++i)scanf("%d",&c[i]);
	sort(c,c+n);
	for(int i=1;i<(1<<n);++i){
		tot=0;
		for(int j=n-1;j>=0;--j){
			if(!((i>>j)&1))continue;
			a[++tot]=c[j];
			b[tot]=dp[i^(1<<j)];
		}
		dp[i]=solve(tot);
	} 
	printf("%.12lf\n",dp[(1<<n)-1]);
} 

你可能感兴趣的:(数学,思维,动态规划)