UOJ 【UR #12】密码锁

本文针对UOJ平台上的密码锁问题进行了深入分析,提出了一种基于强连通分量缩点后的拓扑排序唯一DAG的解决方案。文章详细介绍了如何通过概率计算来确定合法划分方案的数量,并给出了具体的算法实现。

密码锁

Description

http://uoj.ac/problem/181

Data Constraint

1 n 38 , 0 m 19 , 0 wi 104

Solution

由于定向后原图是一个竞赛图,进行强连通分量缩点后,一定是一个拓扑序唯一的 DAG ,此 DAG 的点数的期望值就是答案。

不难发现,此 DAG 可以分成两个集合 S ,T使得 S T之间的所有连边都是 S 连向T的,可以发现,此时的答案就是合法的划分方案数+ 1

于是我们可以将答案写成S,TΠxS yTPx,y其中 Px,y x 指向y的概率。

m =0时,我们可以得出 Ans=1+n1i=1Cin12i(ni)

那当 m 不等于0时,我们可以对特殊边组成的每个弱连通分量分开考虑。
对于一个弱连通分量 K ,我们用2|K|来枚举每个点是属于 S 集还是属于T,若一条特殊边连接的两个端点属于不同的集合,则需要乘上这条边定向的概率。
对于一个弱连通分量我们可以求出一个 f fi表示此弱连通分量内有 i 个点属于S集的总概率之和(不算上非特殊边)。
再设一个 ansi 表示做到当前弱连通分量时总共有 i 个点属于S集时的概率。
我们每做一个弱连通分量就将得到的 f ans卷积以更新 ans

最后答案就为 Ans=1+n1i=1ansi12i(ni)
可以证明时间复杂度的上限为 O(2m+1n+n2)

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=49,M=2e6,mo=998244353;

ll f[N],g[N];
int x[N],y[N],c[N],d[N],m2[N];
int sta[N],fa[N];
int n,m,i,o,p;

inline ll ksm(ll o,ll t)
{
    ll y=1;
    for(;t;t>>=1,o=o*o%mo)
    if(t&1)y=y*o%mo;
    return y;
}

int gf(int a)
{return fa[a]==a?a:fa[a]=gf(fa[a]);}

inline int bits(int o)
{return (!o)?0:(o&1)+bits(o>>1);}

int main()
{
    freopen("random.in","r",stdin);
    freopen("random.out","w",stdout);
    cin>>n>>m;
    fo(i,1,n)fa[i]=i;
    fo(i,1,m){
        scanf("%d%d%d",&x[i],&y[i],&c[i]);
        if(gf(x[i])!=gf(y[i])){
            fa[fa[x[i]]]=fa[y[i]];
        }
    }
    m2[1]=1;
    fo(i,2,n+1)m2[i]=m2[i-1]<<1;
    f[0]=1; ll ny=ksm(10000,mo-2);
    fo(i,1,n)if(fa[i]){
        int v=0;
        fo(l,0,n)g[l]=0;
        fo(l,1,n)if(gf(i)==gf(l))d[++v]=l;
        fo(l,1,v)fa[d[l]]=-1;
        fo(l,0,m2[v+1]-1){
            ll tot=1; int tj=0;
            fo(j,1,v)sta[d[j]]=(l&m2[j])>0;
            fo(j,1,m)if(fa[x[j]]+fa[y[j]]==-2){
            if(sta[x[j]]==1&&sta[y[j]]==0)tot=tot*2*c[j]%mo*ny%mo;
            else if(sta[x[j]]==0&&sta[y[j]]==1)tot=tot*2*(10000-c[j])%mo*ny%mo;
            }
            int gs=bits(l);
            g[gs]=(g[gs]+tot)%mo;
        }
        fd(l,n,0){
            ll op=0;
            fo(j,0,l)op=(op+f[j]*g[l-j])%mo;
            f[l]=op;
        }
        fo(l,1,v)fa[d[l]]=0;
    }
    ll ans=1;
    fo(i,1,n-1)ans=(ans+f[i]*ksm((mo+1)>>1,i*(n-i)))%mo;
    ans=ans*ksm(10000,n*(n-1))%mo;
    cout<<ans; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值