Live2D

2020-10-15 集训题目题解

Game with Probability Problem

题目传送门

题目大意

\(n\) 个石子在这里,Alice 和 Bob 轮流投掷硬币,如果正面朝上,则从 \(n\) 个石子中取出一个石子,否则不做任何事。取到最后一颗石子的人胜利。Alice 在投掷硬币时有 \(p\) 的概率投掷出他想投的一面,Bob 有 \(q\) 的概率投掷出他相投的一面。

现在 Alice 先手投掷硬币,假设他们都想赢得游戏,问你 Alice 胜利的概率为多少。

\(n\le 99999999,0.5\le p,q\le 0.99999999\)

思路

怎么说呢?jb题吧。

首先,我们考虑设 \(f_{n,0/1}\) 表示还剩 \(n\) 颗石子当前为 Alice or Bob 时 Alice 赢 的概率。

然后我们发现如果 \(f_{n-1,1}<f_{n-1,0}\) ,那么两个人都不会翻正面,因为对于 Alice 而言我翻了正面,我赢的概率就变小了,而 Bob 如果翻了那么就会让 Alice 更优。

于是我们可以得到 dp 式:

\[f_{n,0}=p\times f_{n,1}+(1-p)\times f_{n-1,1} \]

\[f_{n,1}=q\times f_{n,0}+(1-q)\times f_{n-1,0} \]

然后解方程即可,这里就不赘述。

接着,我们发现,如果 \(f_{n-1,1}\ge f_{n-1,0}\),那么两个人都会翻正面,证明同上文,于是可以得到 dp 式:

\[f_{n,0}=p\times f_{n-1,1}+(1-p)\times f_{n,1} \]

\[f_{n,1}=q\times f_{n-1,0}+(1-q)\times f_{n,0} \]

解法同上文。

但是,到这一步我们只能做到 \(\Theta(Tn)\),但是我们发现这个东西收敛速率是指数级的,于是我们每次跑到 \(1000\) 就好了。

\(\texttt{Code}\)

int T;read (T);
while (T --> 0){
	int n;scanf ("%d%Lf%Lf",&n,&p,&q);
	f[0][0] = 0,f[0][1] = 1,n = min (n,1000);
	for (Int i = 1;i <= n;++ i){
		double p1 = p,q1 = q;
		if (f[i - 1][1] < f[i - 1][0]) p1 = 1 - p,q1 = 1 - q;
		f[i][0] = (f[i - 1][1] * p1 + f[i - 1][0] * (1 - p1) * q1) / (1 - (1 - p1) * (1 - q1));
		f[i][1] = (f[i - 1][0] * q1 + f[i - 1][1] * (1 - q1) * p1) / (1 - (1 - p1) * (1 - q1));
	}
	printf ("%.6Lf\n",f[n][0]);
}

玩个球

题目传送门

题目大意

有一个 \(01\) 序列,每次可以随机从当前序列中选出一个 \(x\),然后可以从 \(x\) 或者 \(n-x+1\) 中选一个(\(n\) 是当前序列长度)删掉。执行 \(k\) 此操作。问最后 \(1\) 的最大期望值。

一开始序列长度 \(,k\le 30\)

思路

这个题目做出来不难,难的是证明时间复杂度。

这个题目可以直接使用暴力状压,问题就是状态数到底最多有多少个。可以预料到,虽然最坏 \(n\times 2^n\),但是实际上远远达不到。

考虑构造最坏情况。我们发现实际上我们就是构造一个序列使得它的不同子序列个数尽可能大,然后我们发现这个无非是 \(1010101010...\),我们经过验证发现,实际上状态数最多只有 \(72821273\)。足以通过此题。

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define MAXN 31

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int n,k;

double dp[24][1 << 23];
unordered_map <int,double> mp[MAXN];
int tots = 0;
double dfs (int siz,int nowS){
	if (siz == n - k) return 0;tots ++; 
	if (siz > 23 && mp[siz].find (nowS) != mp[siz].end()) return mp[siz][nowS];
	if (siz <= 23 && dp[siz][nowS] != -1.0) return dp[siz][nowS];
	double ans = 0;
	for (Int i = 1;i <= siz / 2;++ i){
		int con1 = nowS >> i - 1 & 1,con2 = nowS >> siz - i & 1;
		int toS1 = nowS & ((1 << i - 1) - 1) | (nowS >> i << i - 1);
		int toS2 = nowS & ((1 << siz - i) - 1) | (nowS >> siz - i + 1 << siz - i);
		ans += 2 * max (dfs (siz - 1,toS1) + con1,dfs (siz - 1,toS2) + con2);
	}
	if (siz & 1){	
		int ind = siz + 1 >> 1,con = nowS >> ind - 1 & 1;
		int toS = nowS & ((1 << ind - 1) - 1) | (nowS >> ind << ind - 1);
		ans += dfs (siz - 1,toS) + con;
	}
	ans /= siz;
	if (siz <= 23) dp[siz][nowS] = ans;
	else mp[siz][nowS] = ans;
	return ans;
}

char s[MAXN];

signed main(){
	read (n,k),scanf ("%s",s + 1);int S = 0;
	for (Int i = 1;i <= n;++ i) S |= (1 << i - 1) * (s[i] == 'W');
	for (Int i = n - k + 1;i <= min (23,n);++ i) for (Int j = 0;j < (1 << i);++ j) dp[i][j] = -1.0;
	printf ("%.6f\n",dfs (n,S));
	cout << tots << endl;//输出状态数 
	return 0;
}

[JLOI2012]时间流逝

题目传送门

题目大意

现在有阈值 \(t\) 以及 \(n\) 个值,有若干轮,每次有 \(p\) 的概率丢掉当前拥有的权值最小值,\(1-p\) 的概率获得一个比当前权值最小值还小的权值。当拥有的权值之和 \(>t\) 之后就会结束。问期望结束轮数。

\(n\le 50\),最多 \(50\) 组数据

思路

不难列出 dp 式,设 \(f_{S}\) 表示拥有权值状态为 \(S\) 时还需的期望轮数,假设 \(S\) 去掉最小权值为 \(nxtS\),可以得到转移式:

\[f_{S}=1+p\times f_{nxtS}+(1-p)/\text{size}(\min\{S\})\sum_{val_x\le \min\{S\}} f_{S\oplus x} \]

我们发现这个式子明显可以用一个套路来转换,我们可以假设 \(f_{S}\) 都可以表示为 \(k\times f_{nxtS}+b\),那么可以将上式变为:

\[f_{S}=1+p\times f_{nxtS}+(1-p)/\text{size}(\min\{S\})\sum_{val_x\le \min\{S\}} (k\times f_{S}+b) \]

(并不保证 \(k,b\) 相同)

\(A=(1-p)/\text{size}(\min\{S\})\)继续变化可以得到:

\[f_S\times (1-A\times (\sum k))=p\times f_{nxtS}+A\times (\sum b)+1 \]

然后你发现我们就做完了。注意四舍五入的细节,具体见代码。

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define MAXN 55

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

struct Node{
	double x,y;
	Node(){}
	Node (double _x,double _y){x = _x,y = _y;}
	Node operator * (double p){return Node (x * p,y * p);}
	Node operator + (const Node &p)const{return Node (x + p.x,y + p.y);}
};

void operator += (Node a,Node b){a = a + b;}
void operator *= (Node a,double b){a = a * b;}

double P;
int T,N,val[MAXN];

Node dfs (int Sum,int Minv){
	if (Sum > T) return Node (0,0);
	double k = 0,b = 0,p = !Sum ? 0 : P,A = (1.0 - p) / Minv;
	for (Int i = 1;i <= Minv;++ i){
		Node ano = dfs (Sum + val[i],i);
		k += ano.x,b += ano.y;
	}
	return Node (p / (1 - A * k),(A * b + 1) / (1 - A * k));
}

signed main(){
	while (~scanf ("%lf",&P)){
		read (T,N);
		for (Int i = 1;i <= N;++ i) read (val[i]);
		sort (val + 1,val + N + 1),printf ("%0.3lf\n",dfs (0,N).y);
	}
	return 0;
}
/*
f[S]=p*f[nxtS]+(1-p)/siz[min{S}]\sum_{x\notin S} f[S|x]+1
f[S]=p/(1-A(\sum k))*f[nxtS]+(A(\sum b)+1)/(1-A\sum k)
*/
posted @ 2020-10-15 22:00  Dark_Romance  阅读(125)  评论(0编辑  收藏  举报