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

题目链接

注:本文参考了洛谷题解,所以有的地方思路没有那么自然,因为我也不知道怎么想到的,就当积累经验了。膜拜 Genius_Star /bx /bx /bx

不太一样的 DAG 容斥题。

直接统计强连通显然是不现实的,应容斥一下求不是强连通的方案数,所以可以联想到 DAG 容斥。

但是这道题不太一样,所以我们设 \(f_s\) 表示点集 \(s\) 是强连通的方案数,那么转移可以用类似 DAG 容斥的方法:

\[f_s=2^{\mathbb{calc}(s,s)}-\sum_{t\subseteq s} (-1)^{val+1} \cdot 2^{\mathbb{calc}(s-t,s)} \]

其中 \(\mathbb{calc}(u,v)\) 表示从点集 \(u\) 出发到点集 \(v\) 的边数,可以通过预处理 \(cnt_{x,u}\) 表示从 \(x\) 出发到点集 \(u\) 的边数,查询 \(O(n)\)。\(val\) 表示 \(t\) 中连通块的数量。

这就是个普通的 DAG 容斥,但是问题是若 \(t\) 内部的边的删除方案不同,\(val\) 也会改变。这很麻烦,所以我们考虑设 \(g_t\) 为点集 \(t\) 的贡献,即 \(t\) 中有奇数个强连通分量的方案数减有偶数个强连通分量的方案数,并凭感觉列出一个转移:

\[g_s=f_s-\sum_{t\subset s} f_t \cdot g_{s-t} \]

这个也是容斥,但仔细思考或写出代码尝试后你会发现这个算重复了,因为无法区分每种方案的强连通分量,于是直接假定 \(\mathbb{lowbit}(s)\) 是 \(t\) 中的强连通分量就行了,即添加条件 \(\mathbb{lowbit}(s)=\mathbb{lowbit}(t)\)。

列出 \(f,g\) 的转移后你会发现 \(f_s,g_s\) 好像互相依赖。我们审视 \(f_s\) 的转移,后面减掉的那一坨是 \(s\) 不是强连通的方案,但你算 \(g_s\) 时算了 \(s\) 是强连通的方案,而且恰好就是通过 \(f_s\) 算的。那么如果你暂且让 \(g_s=-\sum_{t\subset s} f_t \cdot g_{s-t}\),此时 \(g_s\) 就没有考虑 \(s\) 强连通的方案数,可以直接去转移 \(f_s\),转移完 \(f_s\) 在把 \(f_s\) 加在 \(g_s\) 上就行了。

代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 18, maxm = 220, maxc = (1 << 15) + 5;
const int mod = 1e9 + 7;

int n, m, cnt[maxn][maxc];
int f[maxc], g[maxc];
int pow2[maxm];

int calc(int u, int v) {
    int res = 0;
    for(int i = 1; i <= n; i++) if(u & (1 << (i - 1))) res += cnt[i][v];
    return res;
}

int main() {
    scanf("%d%d", &n, &m);
    pow2[0] = 1;
    for(int i = 1; i <= m; i++) pow2[i] = 2ll * pow2[i - 1] % mod;
    int u, v, t = (1 << n) - 1;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d", &u, &v);
        for(int j = 1; j <= t; j++) if(j & (1 << (v - 1))) cnt[u][j]++;
    }
    for(int i = 1; i <= t; i++) {
        for(int j = (i - 1) & i; j; j = (j - 1) & i) {
        	if((i & -i) != (j & -j)) continue;
            g[i] -= 1ll * f[j] * g[i ^ j] % mod;
            if(g[i] < 0) g[i] += mod;
        }
        f[i] = pow2[calc(i, i)];
        for(int j = i; j; j = (j - 1) & i) {
            f[i] -= 1ll * g[j] * pow2[calc(i ^ j, i)] % mod;
            if(f[i] < 0) f[i] += mod;
        }
        g[i] += f[i];
        if(g[i] >= mod) g[i] -= mod;
    }
    printf("%d\n", f[t]);
    return 0;
}
posted @ 2026-09-16 19:39  PrinceEvan  阅读(20)  评论(0)    收藏  举报