• 【洛谷5933】[清华集训2012] 串珠子(子集DP)


    点此看题面

    • 给定一张(n)个点的图,每两个点(i,j)之间要么不连边,要么就有(c_{i,j})种连边方式。
    • 求使这张图连通的连边方案数。
    • (nle16)

    子集(DP)

    很容易想到设(g_S)表示使子集(S)连通的方案数,然而这东西并不好转移,因为同种连通图根据不同的转移顺序很可能会重复计算。

    所以,我们考虑设两个数组来辅助转移,用(f_S)表示子集(S)连边的总方案数,(h_S)表示子集(S)不连通的方案数。

    首先(f_S)的转移是非常容易的,就是连通块中所有(c_{i,j}+1)的乘积。

    (h_S)的转移有一个求解连通块问题方案数的基本套路,就是钦定一个点(这里方便起见可以直接选取(lowbit(x)))把连通块分成与它连通和与它不连通的两部分。

    具体地,我们枚举(S)除去(i)之后的一个子集(T),那么这种情况下的方案数就应该是(g_{Soplus T} imes f_T)

    然后我们用(f_S)减去(h_S)便得到了(g_S)

    代码:(O(n^22^n))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 16
    #define X 1000000007
    using namespace std;
    int n,c[N+5][N+5],f[1<<N],g[1<<N],h[1<<N];
    int main()
    {
    	RI i,j,k,x,l;for(scanf("%d",&n),i=1;i<=n;++i) for(j=1;j<=n;++j) scanf("%d",&c[i][j]);
    	for(l=1<<n,i=0;i^l;++i)//枚举子集
    	{
    		for(f[i]=1,j=1;j<=n;++j) if(i>>j-1&1) for(k=j+1;k<=n;++k) i>>k-1&1&&(f[i]=1LL*f[i]*(c[j][k]+1)%X);//枚举子集中所有边求出f
    		for(x=i^(i&-i),j=x;j;j=(j-1)&x) h[i]=(1LL*g[i^j]*f[j]+h[i])%X;g[i]=(f[i]-h[i]+X)%X;//枚举不与lowbit连通的点集求出g,用f减g求出h
    	}return printf("%d
    ",g[l-1]),0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    记一次mqtt压测过程
    记项目过程中代码分支管理
    测试流程
    Docker与K8s的区别
    Mysql之pymysql
    Mysql常用简介
    JQuery
    CSS
    红外线接受程序 理解
    数码管流水灯升级程序理解
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5933.html
Copyright © 2020-2023  润新知