zoukankan      html  css  js  c++  java
  • UOJ-37 清华集训2014 主旋律

    Description

    给定一场 (n) 个点,(m) 条边的有向图,你可以删去一些边,要求剩下的图仍然强连通。求方案数 (mod (10^9+7)) 的值。

    Hint

    (1le nle 15, 0le mle n(n-1))

    Solution

    orz 神仙容斥

    首先,根据正难则反的思路,合法方案数等于,总方案数 (2^m) 减去不合法方案数。不合法,即将图进行 SCC 缩点之后并不是一个点而是一个 DAG。那么分为两部分:DAG,以及其中的 SCC。

    首先定义 (D(S)) 为点集 (S) 构成的诱导子图中,为 DAG 的生成子图个数。那么构造这个 DAG 是思路是,选定一个点集 (T),作为没有入度的点,从 (T)(S ackslash T) 任意连边。那么一个 naive 思想是((E(V_1,V_2)) 表示出点在 (V_1) 入点在 (V_2) 的边的数量):

    [D(S) = sum_{Tsubseteq Sland T e varnothing} D(Sackslash T) imes 2^{E(T, Sackslash T)} ]

    但这样会算重,比如 (2 o 1, 3 o 1) 这张图,会在计算 (S={1, 2, 3}),枚举子集 (T={2}, {3},{2, 3}) 都会被算一遍。那么用容斥原理解决:

    [D(S) = sum_{Tsubseteq S land T evarnothing} (-1)^{|T|+1}D(Sackslash T) imes 2^{E(T,Sackslash T)} ]

    然后思考怎么算 SCC。首先定义一个 (F(S)) 表示点集 (S) 构成的诱导子图中,为强连通图的生成子图个数(即答案,(F({1, 2, cdots,n})) 即为所求)。

    接下来引入两个记号(没错都是从这里贺的):

    • ( ext{PA}(S:{T}_{i=1}^k)) 表示枚举将 (S) 拆分为 (T_1, T_2, cdots, T_k) 的所有方案,所谓拆分即满足 (cup_{i=1}^k T_i=S) 切对于任意的 (i, jin[1, k])(T_i, T_j) 不交。
    • ( ext{SPA}(S:{T}_{i=1}^k)) 表示枚举 (S) 的严格拆分,即强制要求 (kge 2)

    那么可以列出 (F) 的公式((E(S)) 表示出入点都在 (S) 中的边数,即 (E(S, S))):

    [F(S) = 2^{E(S)} - sum_{ ext{SPA}(S:{T}_{i=1}^k)} D(T) imes prod_{i=1}^k F(T_i) ]

    注意,上面 (D) 的定义虽然是针对点集的,但 (T) 在此应为“点集的点集”,此处可能不够严谨,不妨简单视为 (T) 中的元素本身是缩点后的点,(D(T)) 则是用这些缩完后的点间的边(原图 SCC 之间的边)构成 DAG。然后把 (D(T)) 展开,得:

    [F(S) = 2^{E(S)} - sum_{ ext{SPA}(S:{T}_{i=1}^k)} left( sum_{T'subseteq T land T' evarnothing} (-1)^{|T'|+1}D(Tackslash T') imes 2^{E(T',Tackslash T')} ight) imes prod_{i=1}^k F(T_i) ]

    这个式子丝毫的不可做,但我们仔细观察这两个 (Sigma),是枚举划分再枚举子集。然而我们反过来,先枚举子集,在枚举子集的划分,是完全等价的。那么魔改一下上面的式子:

    [F(S)=2^{E(S)} - sum_{Tsubset S} sum_{ ext{PA}(T:{U}_{i=1}^k)} sum_{ ext{PA}((Sackslash T):{V}_{i=1}^l)} left( (-1)^{k+1} D(V) imes 2^{E(T, Sackslash T)}left(prod _{i=1}^k F(U_i) ight) imes left(prod _{i=1}^l F(V_i) ight) ight) ]

    考虑把 ((-1)^{k+1}prod_{i=1}^k F(U_i)) 提到第二个 (Sigma) 后,发现这样其实可以使 (U,V) 两部分分开。设 (G(S)=sumlimits_{ ext{PA}(S:{T}_{i=1}^k)} (-1)^{k+1} prod_{i=1}^k F(T_i))

    带入原式子得:

    [F(S)=2^{E(S)} - sum_{Tsubset S}G(T) imes 2^{E(T, Sackslash T)} left(sum_{ ext{PA}((Sackslash T):{V}_{i=1}^l)} D(V)prod _{i=1}^l F(V_i) ight) ]

    (2^{E(T, Sackslash T)}) 放后面是为什么呢?仔细观察这个 (sumlimits_{ ext{PA}((Sackslash T):{V}_{i=1}^l)}D (V)prod _{i=1}^l F(V_i)),发现是枚举了 DAG 再将缩掉的点拆开。这相当于什么?是不是任意图?果断换掉这个鸡肋玩意,这样拆分就不用了:

    [F(S)=2^{E(S)} - sum_{Tsubset S}G(T) imes 2^{E(T, Sackslash T)} imes 2^{E(Sackslash T)} ]

    考虑将 (G) 写成可以直接 dp 的式子,避开 ( ext{PA}),即得:

    [G(S)=F(S)-sum_{Tsubset S,uin T} F(T)G(Sackslash T) ]

    其中 (u) 是任取的,在求和过程中固定这么一个 (u) 是为什么呢?首先要知道在没有 (uin T) 时会算重,具体的,对于一张图,有一个 SCC 的点集为 (U),在 (T=U) 时在 (F(T)) 被算了一次,而 (Usubseteq Sackslash T) 时又可能在 (G(Sackslash T)) 中被算一遍。

    为什么 (uin T) 开始让我们不重?考虑两张图相同,必要条件是 (u) 所在 SCC 的编号相同。但这样保证了编号不同,自然就不重了。如果这是正确的,还得保证不漏。首先 (u) 所在 SCC 会被枚举到,而其他部分在 (G(Sackslash T)) 已经确定是可以的了。

    最后 (F,G) 一起 dp 就行了,复杂度最优秀可以 (O(3^n))。然而一次 (O(n)) 计算 (E) 也是可以过的,复杂度 (O(3^n n))

    Code

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : UOJ 37 清华集训2014 主旋律
     */
    #include <iostream>
    
    const int N = 15;
    const int mod = 1e9 + 7;
    typedef long long ll;
    
    inline int& adjust(int& x) {
    	if (x < 0) return x += mod;
    	if (x >= mod) return x -= mod;
    	return x;
    }
    
    int n, m, adj[N];
    int f[1 << N], g[1 << N];
    int pw2[N * N];
    int popc[1 << N], rank[1 << N];
    
    inline int edges(int s, int t) {
    	int ret = 0;
    	for (; s; s -= s & -s)
    		ret += popc[adj[rank[s & -s]] & t];
    	return ret;
    }
    inline int edges(int s) {
    	return edges(s, s);
    }
    
    signed main() {
    	std::cin >> n >> m;
    	for (int i = 1, u, v; i <= m; i++) {
    		std::cin >> u >> v;
    		adj[u - 1] |= (1 << (v - 1));
    	}
    
    	popc[0] = 0;
    	for (int s = 1; s < (1 << n); s++)
    		popc[s] = popc[s >> 1] + (s & 1);
    	for (int i = 0; i < n; i++)
    		rank[1 << i] = i;
    
    	pw2[0] = 1ll;
    	for (int i = 1; i < N * N; i++)
    		pw2[i] = pw2[i - 1] * 2ll % mod;
    
    	for (int s = 1; s < (1 << n); s++) {
    		if (popc[s] == 1) { f[s] = g[s] = 1; continue; }
    		int fix = s & -s, rem = s ^ fix;
    		for (int t = rem; ; t = (t - 1) & rem) {
    			adjust(g[s] = g[s] - (ll)f[(t | fix)] * g[s ^ (t | fix)] % mod);
    			if (!t) break;
    		}
    		for (int t = s; t; t = (t - 1) & s)
    			adjust(f[s] += (ll)g[t] * pw2[edges(s ^ t)] % mod * pw2[edges(s ^ t, t)] % mod);
    		adjust(f[s] = (pw2[edges(s)] - f[s] + mod));
    		adjust(g[s] = (g[s] + f[s]));
    	}
    	printf("%d
    ", f[(1 << n) - 1]);
    	return 0;
    }
    
  • 相关阅读:
    遍历系统进程
    AS3.0实现图像的扭曲
    CMainFrame, View, Doc, App之间的组织
    进程的创建
    自定义异常
    VC6.0快捷键(转载)
    WinMain与应用程序类之间的联系
    解决在Chrome下无法获取showModalDialog返回值的问题
    用js编解码base64
    根据一个绝对路径获取相对路径的方法
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/uoj37.html
Copyright © 2011-2022 走看看