• “蔚来杯“2022牛客暑期多校训练营6 C题: Forest


    C题: Forest

    原题链接:https://ac.nowcoder.com/acm/contest/33191/C

    题目大意

    给定 n ( 1 ≤ n ≤ 16 ) n(1\le n\le 16) n(1n16) 个点 m ( 0 ≤ 100 ) m(0\le 100) m(0100) 条正边权的无向简单图,求每个生成子图的最小生成森林的权值和,答案对 998244353 998244353 998244353 取模。

    定义点集 V V V ,边集 E E E 的图 G G G 的最小生成森林为:

    • 最小生成森林的边集 S ⊆ E S\subseteq E SE
    • 任意两个节点的连通性不变。
    • S S S 为满足以上条件的最小权值。

    G G G 的生成子图为具有点集 V V V 和边集为 E E E 的子集所构成的图。

    题解

    考虑枚举,显然生成子图和最小生成森林都不易枚举,不妨枚举每一条边,计算其在多少张生成子图中作为最小生成森林的边(即作贡献的次数)。

    不妨将所有 m m m 条边按照边权从小到大进行排序,考虑第 i i i 条边 e i e_i ei 的贡献 a n s i ans_i ansi
    ( u i , v i u_i,v_i ui,vi 表示 e i e_i ei 的两个端点, w i w_i wi 表示 e i e_i ei 的权值)
    显然,边权大于 w i w_i wi 的边(即排序后下标 j ∈ [ i + 1 , m ] j\in [i+1,m] j[i+1,m] 的边)是否存在于生成子图中不会对 e i e_i ei 是否作贡献造成影响,只需要在算出前 i i i 条边组成的生成子图中 e i e_i ei 的贡献次数后,乘上系数 2 m − i 2^{m-i} 2mi ( m − i m-i mi 条边选/不选)即可。
    (对于权值相等的边而言,因为答案计算的是权值和,故考虑谁先谁后并不影响,不需要处理相互顺序,可以任意排列)
    对于边权小于 w i w_i wi 的边(即排序后下标 j ∈ [ 1 , i − 1 ] j\in [1,i-1] j[1,i1] 的边),当且仅当他们无法使 u i u_i ui v i v_i vi 联通时, w i w_i wi 作贡献。
    不联通较难计算,正难则反,考虑稍易的 u i u_i ui v i v_i vi 联通的生成子图数。
    用状压 d p dp dp 计数,设 f i , S f_{i,S} fi,S 表示仅含有前 i i i 条边的生成子图中恰有 S S S ( S S S 为一个二进制压缩的点集)一个连通块的方案数。
    考虑从 f i − 1 , S f_{i-1,S} fi1,S 转移到 f i , S f_{i,S} fi,S ,显然,原先每一种已经联通的方案都可以乘上系数 2 2 2 (第 i i i 条边选/不选),并且当第 i i i 条边联通 2 2 2 个原先不联通的连通块时也会产生新的方案,可得转移式如下:
    f i , S = 2 f i − 1 , S + ∑ T ⊂ S , u i ∈ T , v i ∈ S − T f i − 1 , T ∗ f i − 1 , S − T f_{i,S}=2f_{i-1,S}+\sum_{T\subset S,u_i\in T,v_i\in S-T}f_{i-1,T}*f_{i-1,S-T} fi,S=2fi1,S+TS,uiT,viSTfi1,Tfi1,ST

    然后考虑如何计算 a n s i ans_i ansi


    辅助计数,引入 g i , S g_{i,S} gi,S 表示前 i i i 条边中端点均在点集 S S S 中的边数。
    易得转移式:
    g i , S = g i − 1 , S + [ u i ∈ S & v i ∈ S ] g_{i,S}=g_{i-1,S}+[u_i\in S\& v_i\in S] gi,S=gi1,S+[uiS&viS]


    枚举 u i , v i u_i,v_i ui,vi 联通时所在的联通块,此时有三种边:

    • 联通块内的边(已经在 f f f 中考虑)
    • 联通块连向外部节点的边(肯定不能存在,否则联通块不封闭)
    • 外部节点间的边(是否存在皆可,作为系数 2 k 2^k 2k k k k 为外部节点间的边数)

    因而可得前 i − 1 i-1 i1 条边构成联通子图中 u i , v i u_i,v_i ui,vi 不联通的个数为
    2 i − 1 − ∑ S ⊂ V , u i ∈ S , v i ∈ S f i − 1 , S ∗ 2 g i − 1 , V − S 2^{i-1}-\sum_{S\subset V,u_i\in S,v_i\in S}f_{i-1,S}*2^{g_{i-1,V-S}} 2i1SV,uiS,viSfi1,S2gi1,VS

    最终答案:
    a n s i = w i ∗ 2 m − i ∗ ( 2 i − ∑ S ⊂ V , u i ∈ S , v i ∈ S f i − 1 , S ∗ 2 g i − 1 , V − S ) ans_i=w_i*2^{m-i}*(2^i-\sum_{S\subset V,u_i\in S,v_i\in S}f_{i-1,S}*2^{g_{i-1,V-S}}) ansi=wi2mi(2iSV,uiS,viSfi1,S2gi1,VS)


    实际上 f f f g g g 在转移时只需要上一维,可以压缩空间,不过注意 f f f 在转移时要延迟更新。

    参考代码

    #include
    using namespace std;
    
    template<class T>inline void read(T&x){
    	char c,last=' ';
    	while(!isdigit(c=getchar()))last=c;
    	x=c^48;
    	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
    	if(last=='-')x=-x;
    }
    
    const int N=16,M=1e2+5,P=998244353;
    int n,m;
    int f[(1<<N)+5],d[(1<<N)+5];
    int g[(1<<N)+5];
    int pw[M];
    struct node{int u,v,w;}e[M];
    
    bool cmp(node a,node b){return a.w<b.w;}
    
    int main()
    {
    	read(n);
    	for(int i=0;i<n;++i){
    		for(int j=0,x;j<n;++j){
    			read(x);
    			if(i>j&&x){
    				++m;
    				e[m].u=i,e[m].v=j,e[m].w=x;
    			}
    		}
    	}
    	sort(e+1,e+m+1,cmp);
    	for(int i=0;i<n;++i)f[1<<i]=1;//初始化,每个点单独构成联通块
    	pw[0]=1;
    	for(int i=1;i<=m;++i)pw[i]=pw[i-1]*2ll%P;//预处理2的幂
    	int ans=0;
    	for(int i=1,u,v,w;i<=m;++i){
    		u=e[i].u,v=e[i].v,w=e[i].w;
    		int S=(1<<n)-1;
    		S^=1<<u,S^=1<<v;//先去除u和v
    		int tmp=pw[i-1];
    		for(int T=S;~T;T=T?T-1&S:-1){//这一行是枚举所有S的子集,~T判断T是否为-1
    			tmp=(tmp-1ll*f[T|1<<u|1<<v]*pw[g[S^T]])%P;
    			//T|1<
    		}
    		ans=(ans+1ll*w*pw[m-i]%P*tmp)%P;
    		for(int T=S;~T;T=T?T-1&S:-1){//枚举S的子集
    			for(int t=T;~t;t=t?t-1&T:-1){//枚举T的子集
    				d[T|1<<u|1<<v]=(d[T|1<<u|1<<v]+1ll*f[t|1<<u]*f[T^t|1<<v])%P;
    				//d数组用于延迟更新f,t|1<
    			}
    		}
    		for(int T=S;~T;T=T?T-1&S:-1){//枚举S的子集
    			f[T|1<<u|1<<v]=(2ll*f[T|1<<u|1<<v]+d[T|1<<u|1<<v])%P;
    			d[T|1<<u|1<<v]=0;//清空
    			++g[T|1<<u|1<<v];//更新g数组
    		}
    	}
    	cout<<(ans+P)%P<<'\n';
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
  • 相关阅读:
    浅析Linux进程间通信方式之消息队列
    SpringMVC如何使用jstl标签,返回JSON格式的数据
    Hive入门--学习笔记
    数组名是什么
    大语言模型之十五-预训练和监督微调中文LLama-2
    基于java的购物中心商铺管理系统的设计与实现/商铺管理系统
    VUE基础编程(三)
    CPU体系(2):ARM Store Buffer
    我今天拆了个炸弹
    【面试:并发篇39:多线程:线程池】ThreadPoolExecutor类-提交、停止
  • 原文地址:https://blog.csdn.net/Spy_Savior/article/details/126211479