ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[做题笔记] 浅谈状压dp在图计数问题上的应用

2021-08-09 22:02:47  阅读:194  来源: 互联网

标签:二分 方案 浅谈 int tt 状压 集合 计数问题 dp


无向图计数

题目描述

点此看题

有一个 \(n\) 个点 \(m\) 条边的无向图,对于每个 \(k\) 求出有多少种保留边的方案使得 \(1\) 能到 \(k\)

\(n\leq 17,m\leq {n\choose 2}\)

解法

设 \(dp[s]\) 表示 \(1\) 能到集合 \(s\),只考虑集合 \(s\) 中的边的方案数,转移考虑总方案减去不合法的方案,不合法的方案可以枚举一个 \(s\) 的真子集 \(t\),那么 \(s\oplus t\) 和 \(t\) 之间不能有边,\(s\oplus t\) 边任意,设 \(cnt[s]\) 表示 \(2\) 的 \(s\) 内部边数次方:

\[dp[s]=cnt[s]-\sum_{t\in s}dp[t]\times cnt[t-s] \]

可以用 \(\tt FWT\) 优化,时间复杂度 \(O(3^n)/O(2^nn)\)

总结

正难则反是考虑连通性问题的一类重要方法。

#pragma GCC optimize(2)
#include <cstdio>
const int M = 18;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,g[M][M],ans[M],dp[1<<M],cnt[1<<M];
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		int u=read()-1,v=read()-1;
		g[u][v]=g[v][u]=1;
	}
	for(int s=0;s<(1<<n);s++)
	{
		cnt[s]=1;
		for(int i=0;i<n;i++)
			for(int j=i+1;j<n;j++)
				if((s&(1<<i)) && (s&(1<<j)) && g[i][j])
					cnt[s]=2*cnt[s]%MOD;
	}
	dp[0]=1;
	for(int s=1;s<(1<<n);s++)
	{
		if(s&1) dp[s]=cnt[s];
		for(int t=(s-1)&s;t;t=(t-1)&s)
			dp[s]=(dp[s]-dp[t]*cnt[s-t])%MOD;
	}
	for(int s=0;s<(1<<n);s++)
		for(int i=1;i<n;i++)
			if(s&(1<<i))
				ans[i]=(ans[i]+dp[s]*cnt[(1<<n)-1-s])%MOD;
	for(int i=1;i<n;i++)
		printf("%lld\n",(ans[i]+MOD)%MOD);
}

强连通计数

题目描述

点此看题

解法

这个题很好,有向图计数问题可以转 \(\tt DAG\) 计数,因为那东西已经有一套成熟的计算方法了。

设 \(f[s]\) 表示只考虑集合 \(s\) 的点和它们的边的方案数,还是总数减去不合法的方案数,计算不合法的方案数直接转 \(\tt DAG\) 计数,也就是考虑不合法的方案意味着缩点之后构成了 \(\tt DAG\),并且点数大于 \(1\)

那么我们对入度为 \(0\) 的点容斥,设 \(g[s]\) 是钦定集合 \(s\) 中的点入度为 \(0\) 的方案数,转移:

\[g[s]=-\sum_{t\in s}g[t]\times f[s-t] \]

也就是每次新添加一个入度为 \(0\) 的强连通块,为了不算重需要保证 \(f\) 中包含 \(\tt lowbit\) 这一位的点。

设 \(sn[s]\) 表示集合 \(i\) 连向集合 \(s\) 的边,这个每次 \(dp\) 的时候需要在线处理出来,设 \(s[i]\) 表示集合 \(i\) 内部的边数,那么钦定一个子集入度为 \(0\) 来转移,其他的边可以随便选:

\[f[i]=2^{s[i]}-\sum_{j\in i} g[j]\times 2^{s[i]-sn[j]} \]

其中 \(j\) 枚举的是非空子集,转移完 \(f\) 之后让 \(g[i]\leftarrow g[i]+f[i]\) 当作一个小的初始化,时间复杂度 \(O(3^n)\)

#include <cstdio>
const int M = 15;
const int N = 1<<15;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,in[N],out[N],pw[N],cnt[N],s[N],sn[M],f[N],g[N];
void calc(int nw,int i)
{
	int to=(nw-1)&i;
	if(to) calc(to,i);
	sn[nw]=sn[nw-(nw&(-nw))]+cnt[in[nw&(-nw)]&i];
	//sn[nw]=sn[to]+cnt[in[nw-to]&i]
	//what I show above is wrong,think about it
}
signed main()
{
	n=read();m=read();pw[0]=1;
	for(int i=1;i<=m;i++)
	{
		int u=read()-1,v=read()-1;
		in[1<<v]|=(1<<u);
		out[1<<u]|=(1<<v);
		pw[i]=pw[i-1]*2%MOD;
	}
	for(int i=1;i<(1<<n);i++)
		cnt[i]=cnt[i>>1]+(i&1);
	for(int i=1;i<(1<<n);i++)
	{
		int w=i&(-i);
		if(i!=w) s[i]=s[i-w]+cnt[in[w]&i]+cnt[out[w]&i];
	}
	for(int i=1;i<(1<<n);i++)
	{
		calc(i,i);int t=i-(i&(-i));
		for(int j=(i-1)&i;j;j=(j-1)&t)
			g[i]=(g[i]-g[j]*f[i-j])%MOD;
		f[i]=pw[s[i]];
		for(int j=i;j;j=(j-1)&i)
			f[i]=(f[i]-g[j]*pw[s[i]-sn[j]])%MOD;
		g[i]=(g[i]+f[i])%MOD;
	}
	printf("%lld\n",(f[(1<<n)-1]+MOD)%MOD);
}

二分图计数

题目描述

点此看题

给一张 \(n\) 个点 \(m\) 条边的无向图,问有多少种保留边的方案使得最后的图是联通二分图。

\(n\le1 7,m\leq{n\choose 2}\)

解法

这题有两个限制,一个是二分图,一个是联通。

考虑分别解决限制,首先考虑集合 \(s\) 是二分图的方案数,显然的思路是枚举一个子集 \(t\),然后考虑两个集合任意连边,但是这样显然会算重,非连通二分图会被统计多次。

很难去重,但是考虑我们算的方案是具有组合意义的,我们求出的是二分图的染色方案,一个惊为天人的思路是 \(dp\) 二分图的染色方案,最后求出的是连通二分图的染色方案,所以除以 \(2\) 就得到答案了。

设 \(g[s]\) 是集合 \(s\) 的二分图染色方案,\(f[s]\) 是集合 \(s\) 的连通二分图染色方案,然后老正难则反了,我们枚举子集 \(t\),强制它为连通块,为了防止算错我们强制 \(t\) 不包含 \(\tt lowbit\) 那一位:

\[f[s]=\sum_t f[s-t]\times g[t] \]

预处理集合 \(se[s]\) 表示集合 \(s\) 内部的边数,时间复杂度 \(O(2^nm+3^n)\)

#include <cstdio>
const int N = 1<<17;
const int MOD = 998244353;
const int inv2 = (MOD+1)/2;
#define int long long
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,cnt[N],e[N],se[N],g[N],f[N],pw[405];
signed main()
{
	n=read();m=read();pw[0]=1;
	for(int i=1;i<=m;i++)
	{
		int u=read()-1,v=read()-1;
		e[i]=(1<<u)|(1<<v);
		pw[i]=pw[i-1]*2%MOD;
	}
	for(int i=1;i<(1<<n);i++)
		cnt[i]=cnt[i>>1]+(i&1);
	for(int i=0;i<(1<<n);i++)
		for(int j=1;j<=m;j++)
			se[i]+=((i&e[j])==e[j]);
	for(int i=1;i<(1<<n);i++)
	{
		g[i]++;
		for(int j=i;j;j=(j-1)&i)
			g[i]=(g[i]+pw[se[i]-se[j]-se[i^j]])%MOD;
		f[i]=g[i];int t=i-(i&(-i));
		for(int j=t;j;j=(j-1)&t)
			f[i]=(f[i]-g[j]*f[i-j])%MOD;
	}
	printf("%lld\n",(f[(1<<n)-1]*inv2%MOD+MOD)%MOD);
}

标签:二分,方案,浅谈,int,tt,状压,集合,计数问题,dp
来源: https://www.cnblogs.com/C202044zxy/p/15120848.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有