bzoj 5004: 开锁魔法II

翻译后的题意

给你n个点,每个点有且只有一条有向边边
你可以在上面选择k个点,问你选到这k个点,沿着边走,可以走完整个图的概率是多少

题解

很明显,这个图,最后会成为很多个不同的联通块
然后对于每一个联通块,我们缩点之后,就会出现一个树,明显地,树的根是一定要选的
当然,树的根有可能是一个环
然而环里面,你任意选择一个,就可以将环遍历完了
所以,我们可以考虑用 i 个节点,把每一个根都选到,有多少种方案,然后剩下的(ki)个节点,在别的点里面排列组合就好了。。
于是问题就转变成了,现在给你i个选择机会,有n个盒子,每个盒子里面有 ai 个球,问你每个盒子至少选择一个球,有多少种方案
这个的话,怒想了一波排列组合无果
于是就只好用DP了
f[i][j] 表示,前i个盒子,选了j个球,都合法了,有多少种情况
转移也很容易,我就不再赘述了
然后就做完了。。
DP完之后,在枚举一下没多少给根,然后排列组合乘一下就可以了

其他的话

打到一半,我才发现,会炸精度。。
于是有想了半天。。
于是好奇题解有没有排列组合。。
发现他居然直接用double来存。。
smg,300的排列组合居然不怕炸精度?
于是我也试了一下,居然就过了QAQ
看来还是我太年轻了啊

CODE:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N=305;
int T;
struct qq
{
    int x,y,last;
}e[N];
int num,last[N];
int n,k;
void init (int x,int y)
{
    num++;
    e[num].x=x;e[num].y=y;
    e[num].last=last[x];
    last[x]=num;
}
int dfn[N],low[N],sta[N],belong[N],TT[N];
bool in[N]; 
int lalal,cnt,shen;
void dfs (int x)
{
    dfn[x]=low[x]=++lalal;
    sta[++cnt]=x;
    in[x]=true;
    for (int u=last[x];u!=-1;u=e[u].last)
    {
        int y=e[u].y;
        if (dfn[y]==-1)
        {
            dfs(y);
            low[x]=min(low[x],low[y]);
        }
        else if (in[y]) low[x]=min(dfn[y],low[x]);
    }
    if (dfn[x]==low[x])
    {
        shen++;TT[shen]=0;
        int now;
        do
        {
            now=sta[cnt--];
            belong[now]=shen;
            TT[shen]++;
            in[now]=false;
        }while (now!=x);
    }
}
int d[N];
int a[N],tot;//这些必选的盒子里面,有多少个人
int sum;//剩下的有多少个 
double f[N][N];//前i个盒子   选了j个,有多少种方案 
double c[N][N];
void solve ()
{ 
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for (int u=1;u<=tot;u++)
        for (int i=u;i<=n;i++)//一共是多少个
        {
            for (int j=1;j<=min(i,a[u]);j++)//我这里用多少个
                f[u][i]=f[u][i]+f[u-1][i-j]*c[a[u]][j];
        }
    double ans=0;
    for (int u=1;u<=k;u++)//给他多少
        ans=ans+f[tot][u]*c[sum][k-u];
    printf("%.9lf\n",ans/c[n][k]);
}
int main()
{
    scanf("%d",&T);
    c[0][0]=1;
    for (int u=1;u<=300;u++)
    {
        c[u][0]=c[u][1]=1;
        for (int i=1;i<=u;i++)
            c[u][i]=c[u-1][i-1]+c[u-1][i];
    }
    while (T--)
    {
        num=0;memset(last,-1,sizeof(last));
        scanf("%d%d",&n,&k);
        for (int u=1;u<=n;u++)
        {
            int x;
            scanf("%d",&x);
            init(u,x);
        }
        lalal=0;cnt=0;shen=0;
        memset(dfn,-1,sizeof(dfn));
        memset(in,false,sizeof(in));
        for (int u=1;u<=n;u++)
            if (dfn[u]==-1)
                dfs(u);
        memset(d,0,sizeof(d));
        for (int u=1;u<=num;u++)
        {
            int x=e[u].x,y=e[u].y;
            if (belong[x]==belong[y]) continue;
            d[belong[y]]++;
        }
        tot=0;
        sum=n;
        for (int u=1;u<=shen;u++)
            if (d[u]==0)
            {
                a[++tot]=TT[u];
                sum=sum-TT[u];//剩下的有这么多个 
            }
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值