uoj37 - 主旋律 题解

(题面就不吐槽了)

这道 b 以前讲过的题我当时没补(因为觉得太难了),今天被放到了 div2 模拟赛的 T2,而且还一车人做出来了,我感到很离谱,因为今天我再来看这道题仍然觉得很难。


复杂度显然应该跟 \(2^nn^2\) 或者 \(3^n\) 有关,应该是状压 DP。

首先考虑正难则反,用 \(2^m\) 减去至少有两个 scc 的情况数。然后自然而然地想到枚举其中的一个 scc(枚举子集),贡献的因数之一显然是这个子集内断边强连通的方案数,归约到了规模更小的原问题,这就 DP 起来了。但是我们还要保证这个 scc 只有这个子集内的数,也就是不能与其它点成环。这就比较难了,大抵是不可做的。

接下来是比较重要的一步撕烤:把任意一张有向图缩点之后,必然存在一个 scc 代表的点没有入度。那么显然:任意一个不强连通的有向图,缩点之后必然存在一个真子集作为 scc,其缩点后无入度,并且这也是不强连通的充分条件。那么我们就可以枚举子集,钦定其为无入度 scc,那么它不能有其它点连过来的边,那么自然就满足「不能与其它点成环」了。

以及必然地:随便枚举子集会重复,因为一个 dag 可能有若干个无入度点,那就考虑容斥。也就是不强连通图的个数 =「对所有真子集 \(S\),以 \(S\) 作为无入度 scc 之一的有向图集合的并的大小」。于是我们枚举不交的真子集集合,容斥系数是 \(-1\) 的子集个数次方(本来应该是加一次方,但由于最终是要减掉的,就抵消掉了),贡献为这些真子集的 \(f\) 值之积(设 \(f_{msk}\) 表示保留 \(msk\) 中的点后的原题答案)乘以 \(2^{\sum_{(x, y)\in E}[x\in msk-sub,y\in msk]}\)\(msk\) 为当前考虑的集合,\(sub\) 为真子集们的并)。

考虑如何快速求所有 \(msk\) 的不交真子集集合的贡献之和。我们最多能承受 \(\mathrm O\!\left(2^{|msk|}\right)\) 的复杂度,这样总复杂度就是 \(\mathrm O(3^n)\)。容易想到枚举不交真子集们的并 \(sub\),然后搞另一个 DP \(g_{i}\) 表示 \(i\) 的所有划分的 \(-f\) 值之积的和,这个 \(g\) 是和 \(msk\) 无关的,可以跟 \(f\) 的 DP 过程同步更新。然后 \(sub\) 的贡献就是 \(g_{sub}2^{\sum_{(x, y)\in E}[x\in msk-sub,y\in msk]}\)。计算这个指数暴力是平方的,总复杂度就是 \(\mathrm O\!\left(3^nn^2\right)\) 过不了。预处理 \(to_{i,msk}=\sum\limits_{(i, y)\in E}[y\in msk]\) 就可以 \(\mathrm O(n)\) 枚举 \(x\)。进一步,在内部搞一个 \(h_{sub}=\sum\limits_{x\in msk-sub}to_{x,msk}\),可以用 lowbit \(\mathrm O(1)\) 转移,总复杂度 \(\mathrm O\!\left(3^n+2^nn^2\right)\)。当然,由于 \(2^nn^2=\mathrm o(3^n)\),所以复杂度就是 \(\mathrm O(3^n)\)

code
constexpr int N = 30;

int n, m, pw[N * N];
int a[N][N];
int inside[1 << 15];
int to[N][1 << 15];

int f[1 << 15], g[1 << 15];

void mian() {
	pw[0] = 1; REP(i, 1, N * N - 1) pw[i] = add(pw[i - 1], pw[i - 1]);
	n = read(), m = read();
	while(m--) { int x = read(), y = read(); a[x][y] = true; }
	REP(msk, 0, (1 << n) - 1) {
		REP(i, 1, n) if(msk >> i - 1 & 1) REP(j, 1, n) if(msk >> j - 1 & 1) inside[msk] += a[i][j];
		REP(i, 1, n) REP(j, 1, n) if(msk >> j - 1 & 1) to[i][msk] += a[i][j];
	}
	REP(msk, 1, (1 << n) - 1) {
		f[msk] = pw[inside[msk]];
		static int h[1 << 15];
		for(int sub = msk - 1 & msk; sub; sub = sub - 1 & msk) {
			h[msk - sub] = 0;
			h[msk - sub] += to[__builtin_ffs(msk - sub)][msk] + h[msk - sub - lowbit(msk - sub)];
//			REP(i, 1, n) if(msk - sub >> i - 1 & 1) cnt += to[i][msk];
			addto(f[msk], (ll)g[sub] * pw[h[msk - sub]] % P);
		}
		int x = __builtin_ffs(msk);
		for(int sub = msk - 1 & msk; sub; sub = sub - 1 & msk)
			if(sub >> x - 1 & 1) addto(g[msk], -(ll)f[sub] * g[msk - sub] % P);
		addto(f[msk], g[msk]);
		addto(g[msk], -f[msk]);
//		debug(mt(msk, f[msk], g[msk]));
	}
	prt(f[(1 << n) - 1]), pc('\n');
}
posted @ 2022-02-12 19:17  ycx060617  阅读(236)  评论(0编辑  收藏  举报