【[JLOI2013]卡牌游戏】

思路太妙了

刚开始yy出了一种比较自然的dp方法,就是按照游戏的进行来开始dp,设\(dp[i][j]\)表示第\(i\)个人为庄家,还剩下\(j\)个人的概率为多少,但是很快发现这个样子没法转移,因为没有办法确定下一个庄家是谁

于是只能将第二维压成一个状态\(s\) ,\(dp[i][s]\)表示第\(i\)个人为庄家存活状态为\(s\)的概率为多少,显然这个样子做一个状压dp的话我们可以枚举当前的庄家,当前的状态,以及当前的庄家用的牌是哪一张,之后我们就可以确定下一个庄家是谁,这样就可以转移了

但是这个复杂度大概是\(O(nm2^n)\),30%的数据应该还是能过的

正解的思路就相当秒了,我们设\(dp[i][j]\)表示有\(i\)个人参与游戏,从庄家(即1)数\(j\)个人获胜的概率是多少

初始状态\(dp[1][1]=1\)

之后我们枚举\(i,j\),之后继续枚举\(k\)表示庄家(即1)选择了第\(k\)张牌,由于我们这里的庄家都是1所以我们要通过模拟第一步进行状态的转移

我们的到第一步之后就可以转化为\(dp[i-1][]\)

我们枚举了\(k\)自然就可以得到第一轮被淘汰的人\(p\)

如果\(p=j\)\(j\)上来就被淘汰就不用转移了

而第 \(p\)个人被淘汰之后,剩下的 \(i-1\) 个人要组成一个新的环,庄家为第 \(p\)个人的下一个。容易算出,当 \(p>j\) 时,第 \(j\)个人是新的环里从新庄家数起的第 \(i-p+j\) 个人,当 \(c<j\) 时,第 \(j\) 个人是新的环里从新庄家数起的第 \(j-p\)个人。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 55
int n,m;
int a[maxn];
double dp[maxn][maxn];
inline int read()
{
	char c=getchar();
	int x=0;
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	  x=(x<<3)+(x<<1)+c-48,c=getchar();
	return x;
}
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=m;i++)
		a[i]=read();
	dp[1][1]=1.0;
	for(re int i=2;i<=n;i++)
	{
		for(re int j=1;j<=i;j++)
		{
			for(re int k=1;k<=m;k++)
			{
				int p=a[k]%i;
				if(!p) p=i;
				if(p>j) dp[i][j]+=dp[i-1][i-p+j]/m;
				if(p<j) dp[i][j]+=dp[i-1][j-p]/m;
			}
		}
	}
	for(re int i=1;i<=n;i++)
	printf("%.2lf",dp[n][i]*100),putchar('%'),putchar(' ');
	return 0;
}
posted @ 2019-01-02 11:57  asuldb  阅读(127)  评论(0编辑  收藏  举报