【ybtoj】【背包问题】魔法开锁

题意

题解

此题坑还是很大的。

一开始看到题目所说概率云云,联想到的是类似期望DP之类的方法,苦思冥想之后放弃,几乎没有思路

首先需要转化问题:求出“选出 t 个点覆盖掉所有的环”的方案数和所有选择的方案数(也就是从 n 个点中选 t 个点,即C(n,m))

那么我们先预处理出组合数的递推

void init()
{
	c[0][0]=1;
	for(int i=1;i<=300;i++)	
	{
		c[i][0]=1;
		for(int j=1;j<=i;j++) 
			c[i][j]=c[i-1][j]+c[i-1][j-1];
	}
}

再找到所有的环并求出其大小

for(int i=1;i<=n;i++)
{
	if(!vis[i]) 
	{
		int now=i,tot=0;
		while(1)
		{
			if(vis[now]) break;
			vis[now]=1,tot++;
			now=a[now];
		}
		cir[++cnt]=tot;
	}
}

设计 dp[i][[j]表示前 i 个环里选了 j 个点的方案数,就可以写出类似背包的转移:dp[i][j]+=dp[i-1][k]*c[cir[i]][j-k] (cir表示环的大小)

注意转移时的边界 0<=k<j,因为每个环都至少选一个点

	dp[0][0]=1;
	for(int i=1;i<=cnt;i++)
		for(int j=0;j<=t;j++)
			for(int k=0;k<j;k++)
				dp[i][j]+=dp[i-1][k]*c[cir[i]][j-k];
	

Tips:虽然只有最后一步除法求概率用到了double,但是 dp,c 等数组都要开成double。因为组合数范围比较大,所以会爆 long long,而double的上界是10308,可以通过

完整代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define db double 
const int INF = 0x3f3f3f3f,N = 305;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
db a[N],c[N][N],dp[N][N];
ll cnt,cir[N],T,n,t;
bool vis[N];
void init()
{
	c[0][0]=1;
	for(int i=1;i<=300;i++)	
	{
		c[i][0]=1;
		for(int j=1;j<=i;j++) 
			c[i][j]=c[i-1][j]+c[i-1][j-1];
	}
}
void solve()
{
	memset(vis,0,sizeof(vis));
	memset(cir,0,sizeof(cir));
	memset(dp,0,sizeof(dp));
	cnt=0;
	n=read(),t=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++)
	{
		if(!vis[i]) 
		{
			int now=i,tot=0;
			while(1)
			{
				if(vis[now]) break;
				vis[now]=1,tot++;
				now=a[now];
			}
			cir[++cnt]=tot;
		}
	}
	dp[0][0]=1;
	for(int i=1;i<=cnt;i++)
		for(int j=0;j<=t;j++)
			for(int k=0;k<j;k++)
				dp[i][j]+=dp[i-1][k]*c[cir[i]][j-k];
	
	printf("%.9lf\n",1.0*dp[cnt][t]/c[n][t]);
}
int main()
{
	T=read();
	init();
	while(T--) solve();
	return 0;
}

 

posted @ 2021-09-09 07:38  conprour  阅读(111)  评论(0编辑  收藏  举报