P11714 [清华集训 2014] 主旋律 题解

P11714 [清华集训 2014] 主旋律 题解


知识点

状压计数 DP。

题意简述

一个 \(n\) 个点,\(m\) 条边的有向图,现在删除一些边,求删除边后图为强联通的方案数,答案对 \(10^9+7\) 取模。

分析

考虑单步容斥,得到「图为强联通的方案数」就等于「总方案数」减去「图不为强联通的方案数」,所以我们只要求「图不为强联通的方案数」即可。

考虑将图 SCC 缩点,会得到点数大于 \(1\) 的 DAG,所以现在有一种较暴力的做法:枚举每个点所在的 SCC,然后求 「DAG 生成子图方案数」。

「DAG 生成子图方案数」

这类有关 DAG 的计数通常都可以往加入入度为 \(0\) 的点这个方向想。

\(f_{S}\) 表示点集为 \(S\) 时的「DAG 生成子图方案数」,可以列一个简单的 DP 转移式:(\(e(T,S)\) 表示「从点集 \(T\)\(S\) 的边数」)

\[f_S = \sum_{T\subseteq S,T\neq \emptyset} 2^{e(T,S\setminus T)} f_{S\setminus T} \\ \]

但是这看着就不对……因为在点集 \(S\setminus T\) 中可能存在入度为 \(0\) 的点,这样就算重复了。

  • \(F_{T}\) 表示「点集 \(S\) 中入度为 \(0\) 的点组成的集合恰好为 \(T\) 的方案数」。
  • \(G_{T}\) 表示「点集 \(S\) 中入度为 \(0\) 的点组成的集合包含点集 \(T\) 的方案数」。

根据定义可以推得:

\[f_S = \sum_{T\subseteq S,T\neq \emptyset} F_T \\ G_T = 2^{e(T,S\setminus T)} f_{S\setminus T} \\ \]

而我们接下来可以在 \(F_T\)\(G_T\) 之间进行二项式反演:

\[G_T = \sum_{T\subseteq V} F_V \\ \Updownarrow \\ F_T = \sum_{T\subseteq V} (-1)^{|V|-|T|} G_V \\ \]

代入原式化简:

\[\begin{aligned} f_S &= \sum_{T\subseteq S,T\neq \emptyset} F_T \\ &= \sum_{T\subseteq S,T\neq \emptyset} \sum_{T\subseteq V} (-1)^{|V|-|T|} G_V \\ &= \sum_{T\subseteq S,T\neq \emptyset} (-1)^{|T|} \sum_{T\subseteq V} (-1)^{|V|} G_V \\ &= \sum_{V\subseteq S,V\neq \emptyset} (-1)^{|V|} G_V [\sum_{T\subseteq V,T\neq \emptyset} (-1)^{|T|}] \\ \end{aligned} \]

二项式定理推得 \(\sum_{T\subseteq V,T\neq \emptyset} (-1)^{|T|} = -1\)

\[\begin{aligned} f_S &= \sum_{V\subseteq S,V\neq \emptyset} (-1)^{|V|} G_V [\sum_{T\subseteq V,T\neq \emptyset} (-1)^{|T|}] \\ &= \sum_{V\subseteq S,V\neq \emptyset} (-1)^{|V|+1} G_V \\ &= \sum_{V\subseteq S,V\neq \emptyset} (-1)^{|V|+1} 2^{e(T,S\setminus T)} f_{S\setminus T} \\ \end{aligned} \]

这个可以 \(O(3^n)\) 复杂度内计算出来,但是明显这不是正解,于是我们尝试仿照上述过程进行推广,直指我们的本来目的。

「SCC 缩点方案数」

仿照上文,就可以设出以下状态:

  • \(f_{S}\) 表示「缩点后点集 \(S\) 为一个 SCC 的方案数」。
  • \(g_T\) 表示「缩点后点集 \(T\) 为若干个入度为 \(0\) 的 SCC 的方案数」。

那么我们考虑列式:

\[f_S = 2^{e(S,S)} - [\sum_{T\subseteq S,T\neq \emptyset} (-1)^{z_T} 2^{e(T,S\setminus T)+e(S\setminus T,S\setminus T)} g_T] + f_S \\ \]

最后的这个 \(f_S\) 多出来是因为枚举子集 \(T\) 时,当 \(T=S\),那么会导致 \(g_T\) 中包含 \(f_T\),这部分其实是合法的,不应该减掉。

发现 \(z\) 是不能确定的,因为在我们设状态时,就没有限定 SCC 的数量,所以更改 \(g_T\) 的定义,把容斥系数也放进去,即「缩点后点集 \(T\) 为若干个入度为 \(0\) 的 SCC 的方案中,缩成奇数个的方案数减去缩成偶数个的」。式子变为:

\[f_S = 2^{e(S,S)} - [\sum_{T\subseteq S,T\neq \emptyset} 2^{e(T,S\setminus T)+e(S\setminus T,S\setminus T)} g_T] + f_S \\ \]

(式中 \(f_S\) 还是多了出来,式子好像变成了恒等式,这个怎么处理?)

接下来求 \(g_T\),这个不难想,只要找子结构即可:

\[g_S = f_S - \sum_{T\subsetneq S,\operatorname{lowbit}(S) = \operatorname{lowbit}(T)} f_T g_{S\setminus T} \\ \]

  • \(f_S\):表示当只有一个 SCC 时的方案数。
  • \(\sum_{T\subsetneq S,\operatorname{lowbit}(S) = \operatorname{lowbit}(T)} f_T g_{S\setminus T}\):增加一个包含 \(\operatorname{lowbit}(S)\) 的 SCC,总数奇偶性改变,符号也改变。

发现此时 \(f_S\)\(g_S\) 互相依赖,而且 \(f_S\) 的式子好像变成了恒等式,我们不知道该怎么求。

但是再细想,发现此时上式中多出来的 \(f_S\) 就有了用武之地:求 \(f_S\) 时,\(g_S\) 转移式中的那个 \(f_S\) 可以先不加,这样 \(f_S\) 转移式中多出来的 \(f_S\) 也可以不加了。所以先算 \(g_S\),但不加 \(f_S\);然后再算 \(f_S\),最后加到 \(g_S\) 中去。

「从点集 \(T\)\(S\) 的边数」

发现最后剩下一个小问题,如何预处理出 \(e(S,T)\)

考虑总状态数,对于 \(S\),只会用到 \(e(S,S)\) 或者 \(e(T,S\setminus T)(T\subseteq S)\),那么总共只有 \(3^n\) 个,那么预处理可以考虑 vector 开到 \(O(3^n)\) 的空间,也可以对每个 \(S\) 都单独处理。

求出 \(S\) 不变时的递推式,设 \(c(T)=e(T,S\setminus T)\):(\(in_u\) 表示连到 \(u\) 的点集,\(out_u\) 表示 \(u\) 连到的点集)

\[x\in (S\setminus T),c(T) = c(T\cup \set{x}) + |in_x \cup T| - |out_x \cup (S\setminus T)| \\ \]

然后还要单独求出 \(e(S,S)\) 的递推式,我们设 \(d(S) = e(S,S)\)

\[x\in S,d(S) = d(S\setminus \set{x}) + |in_x \cup S| +|out_x \cup S| \\ \]

在编码时,这里的 \(x\) 都取 \(\operatorname{lowbit}\) 即可。

代码

//#define Plus_Cat ""
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define lowbit(x) ((x)&-(x))
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i=(g).h[(x)],y=(g)[(i)].v;~i;y=(g)[(i=(g)[i].nxt)>0?i:0].v)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);return Main();}signed Main
using namespace std;
constexpr int N(15+10),M(210+10),St((1<<15)+10);

namespace Modular {
#define Mod 1000000007
	int pw[M<<1];
    //Modular...
} using namespace Modular;

int n,m,U;
int in[N],out[N],c[St],d[St],f[St],g[St],Lg[St],cnt[St];

signed main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	cin>>n>>m,U=(1<<n)-1;
	FOR(i,pw[0]=1,m<<1)pw[i]=mul(pw[i-1],2);
	FOR(i,1,m) {
		int u,v;
		cin>>u>>v,out[u]|=1<<(v-1),in[v]|=1<<(u-1);
	}
	FOR(i,1,n)Lg[1<<(i-1)]=i;
	FOR(S,1,U)cnt[S]=cnt[S>>1]+(S&1);
	FOR(S,1,U)d[S]=d[S^lowbit(S)]+cnt[S&in[Lg[lowbit(S)]]]+cnt[S&out[Lg[lowbit(S)]]];
	FOR(S,1,U) {
		c[S]=0,f[S]=pw[d[S]];
		for(int T((S-1)&S); T; T=(T-1)&S) {
			int x(lowbit(S^T));
			c[T]=c[T|x]-cnt[(S^T)&out[Lg[x]]]+cnt[T&in[Lg[x]]];
		}
		for(int R(S^lowbit(S)),T((R-1)&R); ~T; T=T?(T-1)&R:-1)toadd(g[S],Mod-mul(f[T|lowbit(S)],g[R^T]));
		for(int T(S); T; T=(T-1)&S)toadd(f[S],Mod-mul(g[T],pw[c[T]+d[S^T]]));
		toadd(g[S],f[S]);
	}
	cout<<f[U]<<endl;
	return 0;
}

posted @ 2025-06-05 14:13  Add_Catalyst  阅读(15)  评论(0)    收藏  举报