BZOJ 5004: 开锁魔法II 概率dp



盒子之间的开启关系 用图的方式呈现 就是一片环

最终所有成功开启 则每一颗环都成功开启

所以求出每个环放k个点成功的概率

之后依次枚举每一颗环 及其内部选择点数与在之前的环中选择点数 再乘上对应概率


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;

typedef double db;
typedef long long ll;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=310;

int last[N],ecnt;
struct EDGE{int to,nt;}e[N];
inline void add(int u,int v)
{e[++ecnt]=(EDGE){v,last[u]};last[u]=ecnt;}

int Fa[N];
int find(int x)
{return Fa[x]==x ? x : Fa[x]=find(Fa[x]);}

bool book[N];
int dep[N],size[N];
int len;

void dfs(int u)
{
	size[u]=book[u]=1;
	for(int i=last[u],v;i;i=e[i].nt)
	{
		if(book[v=e[i].to])
		{
			len=dep[u];
			continue;
		}
		dep[v]=dep[u]+1;
		dfs(v);
		size[u]+=size[v];
	}
}

int st[N],num[N],top;

db p[N][N],f[N],g[N],tmp[N][N];

int main()
{
	register int T=read(),i,j,k,x;
	while(T--)
	{
		int n=read(),K=read();
		for(i=1;i<=n;++i) Fa[i]=i;
		ecnt=0;memset(last,0,sizeof(last));
		for(i=1;i<=n;++i)
			x=read(),add(i,x),
			Fa[find(x)]=find(i);
		
		if(K==0){puts("0.000000000");continue;}
		else if(K==n){puts("1.000000000");continue;}
		
		top=0;
		for(i=1;i<=n;++i)
			if(find(i)==i)
				st[++top]=i;
		memset(book,0,sizeof(book));
		for(i=1;i<=top;++i)
			dep[st[i]]=1,dfs(st[i]),num[i]=len;
		
		db now;
		for(i=1;i<=top;++i)
		{
			for(j=0,now=1;j<=size[st[i]]-num[i];++j,now*=(1.0*(size[st[i]]-num[i]-j+1)/(size[st[i]]-j+1)))
				p[i][j]=1-now;
			for(j=size[st[i]]-num[i]+1;j<=size[st[i]];++j)
				p[i][j]=1;
		}
		
		memset(f,0,sizeof(f));
		f[0]=1;
		int s,t(0);
		for(i=1;i<=top;++i)
		{
			memcpy(g,f,sizeof(f));
			memset(f,0,sizeof(f));
			s=size[st[i]];
			tmp[0][0]=1;
			for(j=1;j<=s;++j)
				tmp[j][0]=tmp[j-1][0]*(s-j+1)/(s+t-j+1);
			for(j=1;j<=t;++j)
				tmp[0][j]=tmp[0][j-1]*(t-j+1)/(s+t-j+1);
			for(j=1;j<=s;++j)
				for(k=1;k<=t;++k)
					tmp[j][k]=tmp[j-1][k]*(s-j+1)/(s+t-k-j+1)+tmp[j][k-1]*(t-k+1)/(s+t-k-j+1);
			for(j=1;j<=s;++j)
				for(k=i-1;k<=t;++k)
					f[k+j]+=p[i][j]*g[k]*tmp[j][k];
			t+=s;
		}
		
		printf("%.9lf\n",f[K]);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值