题解 爆搜

传送门

好题!

首先有一个 \(O(n^22^n)\) 的做法是每次枚举两个度数为 0 的点连边
然后就完全没思路了

image

啊确实不是人想的
那么这个题解在说什么呢?
猜测在连上虚边后每个合法的环和链的方案唯一对应原图中一个匹配
那么证明一下:发现若强制虚边必选,那么环和链的每个方案在删/加虚边的转换下是一一对应的(因为加虚边的策略是固定的)
那么就是求将 \(2i-1, 2i\) 间连上虚边后形成一些环和链的方案数了
\(bel_{2i-1}=bel_{2i}=i\) ,因为 \(2i-1\)\(2i\) 要么同时在图里要么同时不在,所以状态只需要压 \(2^{\frac{n}{2}}\)
\(f_{s, i}\) 为已使用 \(s\) 中点集,当前链的端点为 \(i\) 的方案数,转移枚举加入点对
\(g_{s, i, j}\) 为已使用 \(s\) 中点集,环中待闭合端点(为了防止算重钦定它是环中最大的点)为 \(i\),当前端点为 \(j\) 的方案数
转移枚举加一个 \(<i\) 的点对或将环闭合
那么如何合并呢?
一个简单的想法是令 \(dp_{s, i, j}\) 为点集 \(s\) 中有 \(i\) 个连通块,形成了 \(j\) 个匹配的方案数
一个优化是要求的东西是 \(c^{|S|}\),那么存方案数的话要开一维存匹配数,直接存 \(\sum c^{\text{形成的匹配数}}\) 就可以优化掉一个 \(n\)
然后就是连通块数不必记录
对于思维定式的我来说一个显然的想法是转移是钦定要包含当前为选的最小的点
然而这样算只有 \(dp_{全集}\) 是对的
那么对于一个转移 dp[s]+=dp[s^s0]*f[s0],钦定 \(s_0\) 包含 \(s\) 的最低位就可以对任意集合求出正确结果了
复杂度 \(O(n^22^{\frac{n}{2}}+3^{\frac{n}{2}})\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define fir first
#define sec second
#define pb push_back
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m, c;
vector<int> to[N];
pair<int, int> e[N];
const ll mod=1e9+7, inv2=(mod+1)>>1;
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod,b>>=1) if (b&1) ans=ans*a%mod; return ans;}

namespace force{
	ll ans;
	int cnt[N];
	bool vis[N];
	void dfs(int u, int tot) {
		if (u>n) {++cnt[tot]; return ;}
		if (vis[u]) {dfs(u+1, tot); return ;}
		else {
			vis[u]=1;
			dfs(u+1, tot);
			for (auto v:to[u]) if (!vis[v]) {
				vis[v]=1; dfs(u+1, tot+1); vis[v]=0;
			}
			vis[u]=0;
		}
	}
	void solve() {
		dfs(1, 0);
		// cout<<"cnt: "; for (int i=0; i<=n; ++i) cout<<cnt[i]<<' '; cout<<endl;
		for (int i=0; i<=n; ++i) ans=(ans+cnt[i]*qpow(c, i))%mod;
		cout<<ans<<endl;
	}
}

namespace task1{
	ll cnt[N], f[2][1<<20][20], now, ans;
	inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}
	void solve() {
		f[now][0][0]=1;
		for (int i=1; i<=m; ++i) e[i].fir-=1, e[i].sec-=1;
		int lim=1<<n;
		for (int i=1; i<=m; ++i,now^=1) {
			memset(f[now^1], 0, sizeof(f[now^1]));
			for (int s=0; s<lim; ++s) {
				for (int j=0; j<n; ++j) if (f[now][s][j]) {
					if ((!(s&(1<<e[i].fir))) && (!(s&(1<<e[i].sec)))) md(f[now^1][s|(1<<e[i].fir)|(1<<e[i].sec)][j+1], f[now][s][j]);
					md(f[now^1][s][j], f[now][s][j]);
				}
			}
		}
		for (int s=0; s<lim; ++s)
			for (int j=0; j<n; ++j)
				md(cnt[j], f[now][s][j]);
		for (int i=0; i<=n; ++i) ans=(ans+cnt[i]*qpow(c, i))%mod;
		cout<<ans<<endl;
	}
}

namespace task{
	ll f[1<<18][40], sumf[1<<18], g[1<<18][40], sumg[1<<18], dp[1<<18], cnt[N], pw[N], ans;
	int bel[N], mate[N], tot;
	void solve() {
		// cout<<double(sizeof(g))/1000/1000<<endl;
		pw[0]=1;
		for (int i=1; i<=n; ++i) pw[i]=pw[i-1]*c%mod;
		if (n&1) ++n; tot=n/2;
		for (int i=1; i<=n; i+=2) bel[i]=bel[i+1]=i>>1, mate[mate[i]=i+1]=i;
		for (int i=1; i<=n; ++i) f[1<<bel[i]][i]=1;
		int lim=1<<tot;
		for (int s=1; s<lim; ++s)
			for (int i=1; i<=n; ++i) if (f[s][i])
				for (auto v:to[i]) if (!(s&(1<<bel[v])))
					md(f[s|(1<<bel[v])][mate[v]], f[s][i]);
		for (int s=0; s<lim; ++s) if (__builtin_popcount(s)>1) {
			for (int i=1; i<=n; ++i) sumf[s]=(sumf[s]+f[s][i]*pw[__builtin_popcount(s)-1])%mod;
			sumf[s]=sumf[s]*inv2%mod;
		}
		// for (int s=0; s<lim; ++s)
		// 	for (int i=1; i<=n; ++i)
		// 		cout<<"f: "<<bitset<5>(s)<<' '<<i<<' '<<f[s][i]<<endl;
		// for (int i=2; i<=n; i+=2) g[1<<bel[i]][i][mate[i]]=1;
		for (int i=2; i<=n; i+=2) {
			memset(g, 0, sizeof(g));
			g[1<<bel[i]][mate[i]]=1;
			for (int s=1; s<lim; ++s)
				for (int j=1; j<=n; ++j) if (g[s][j])
					for (auto v:to[j])
						if (v<i && !(s&(1<<bel[v])))
							md(g[s|(1<<bel[v])][mate[v]], g[s][j]); //, cout<<"from: "<<bitset<5>(s)<<' '<<j<<" ("<<g[s][j]<<")"<<" to "<<bitset<5>(s|(1<<bel[v]))<<' '<<v<<' '<<mate[v]<<endl;
						else if (v==i) sumg[s]=(sumg[s]+g[s][j]*pw[__builtin_popcount(s)])%mod; //, cout<<"add: "<<bitset<5>(s)<<' '<<i<<' '<<j<<' '<<g[s][j]<<endl;
			// for (int s=0; s<lim; ++s)
			// 	cout<<"g: "<<bitset<5>(s)<<' '<<i<<' '<<g[s][i]<<endl;
		}
		// for (int s=0; s<lim; ++s) cout<<"sumf: "<<bitset<5>(s)<<' '<<sumf[s]<<endl;
		// for (int s=0; s<lim; ++s) cout<<"sumg: "<<bitset<5>(s)<<' '<<sumg[s]<<endl;
		dp[0]=1;
		for (int s=0; s<lim; ++s)
			for (int s0=s; s0; s0=s&(s0-1)) if ((s0&-s0)==(s&-s))
				dp[s]=(dp[s]+dp[s^s0]*(sumf[s0]+sumg[s0]))%mod; //, cout<<bitset<5>(s)<<' '<<bitset<5>(s0)<<endl;
		for (int s=0; s<lim; ++s) md(ans, dp[s]); //, cout<<"dp: "<<bitset<5>(s)<<' '<<dp[s]<<endl;
		cout<<ans<<endl;
	}
}

signed main()
{
	freopen("dfs.in", "r", stdin);
	freopen("dfs.out", "w", stdout);

	n=read(); m=read(); c=read();
	for (int i=1,u,v; i<=m; ++i) {
		u=read(); v=read();
		e[i]={u, v};
		to[u].pb(v); to[v].pb(u);
	}
	// force::solve();
	// task1::solve();
	task::solve();

	return 0;
}
posted @ 2022-03-22 21:43  Administrator-09  阅读(11)  评论(0编辑  收藏  举报