UOJ #181. 【UR #12】密码锁

12 篇文章 0 订阅

一个竞赛图,其中m条边,方向为 x>y(x<y) x − > y ( x < y ) 的概率是 pi p i y>x y − > x 的概率是 1pi 1 − p i ,其他边两个方向的概率都是 12 1 2
求强连通分量的期望个数

竞赛图缩点后一定是一条链,前面的点连向后面所有点,我们定义点集 S S 是这条链的一个前缀当且仅当SV,SVSSVS
那么强连通分量个数等价于缩点后链的前缀 S S 的个数+1

一种想法是枚举点集S,计算这些边的贡献,但 n n 很大,2n不能接受
一个大小为 x x 的前缀,如果连出去的没有特殊边,他的贡献是(12)x(nx),每有一条概率为 p p 的特殊边,贡献乘上2p
因为 m m 很小,一个m条边的联通块至多 m+1 m + 1 个点,我们只考虑特殊边,对每个联通块枚举 S S 里的点,计算S x x <script id="MathJax-Element-21" type="math/tex">x</script>个点在这个联通块里的贡献和
最后再对所有联通块做个背包合并起来

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 50;
const int mod  = 998244353;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}

int pw(int x,int k)
{
    int re=1;
    for(;k;k>>=1,x=(ll)x*x%mod) if(k&1)
        re=(ll)re*x%mod;
    return re;
}
int inv(int x){ return pw(x,mod-2); }

int n,m,Inv;
int e[maxn][4];
struct edge{int y,i,nex;}a[maxn*maxn]; int len,fir[maxn];
inline void ins(const int x,const int y,const int i){a[++len]=(edge){y,i,fir[x]};fir[x]=len;}

vector<int>V[maxn],Ve[maxn];
int vis[maxn],cnt,id[maxn];
void dfs(const int x)
{
    V[vis[x]=cnt].push_back(x); id[x]=(int)V[cnt].size();
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
        if(x<y) Ve[cnt].push_back(a[k].i);
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!vis[y])
        dfs(y);
}
int sum[1<<20],g[maxn][maxn];
void Solve(const int now)
{
    int vn=(int)V[now].size(),al=1<<vn;
    for(int i=0;i<al;i++)
    {
        int tc=1;
        for(int j=0;j<(int)Ve[now].size();j++)
        {
            int k=Ve[now][j],x=e[k][0],y=e[k][1];
            if((i>>(id[x]-1)&1)^(i>>(id[y]-1)&1))
                tc=(ll)tc*((i>>(id[x]-1)&1)?e[k][2]:e[k][3])%mod*2ll%mod;
        }
        sum[i]=sum[i>>1]+(i&1);
        add(g[now][sum[i]],tc);
    }
}
int f[maxn],temp[maxn];

int main()
{
    Inv=inv(10000);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y,w; scanf("%d%d%d",&x,&y,&w);
        e[i][0]=x,e[i][1]=y; w=(ll)w*Inv%mod;
        e[i][2]=w;e[i][3]=(1-w+mod)%mod;
        ins(x,y,i); ins(y,x,i);
    }
    for(int i=1;i<=n;i++) if(!vis[i])
    {
        ++cnt,dfs(i);
        Solve(cnt);
    }

    f[0]=1; int nowsum=0;
    for(int i=1;i<=cnt;i++)
    {
        for(int j=0;j<=nowsum+(int)V[i].size();j++) temp[j]=0;
        for(int j=0;j<=nowsum;j++) for(int k=0;k<=(int)V[i].size();k++)
            add(temp[j+k],(ll)f[j]*g[i][k]%mod);
        nowsum+=(int)V[i].size();
        for(int j=0;j<=nowsum;j++) f[j]=temp[j];
    }
    int ans=0,inv2=inv(2);
    for(int i=1;i<nowsum;i++)
        add(ans,(ll)f[i]*pw(inv2,i*(nowsum-i))%mod);
    add(ans,1);
    ans=(ll)ans*pw(10000,n*(n-1))%mod;
    printf("%d\n",ans);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值