P6651 Chain

我们要在不经过某些点的情况下找出剩余链的多少比较困难,所以正难则反,变为求出经过这 \(k\) 的点的链的多少,然后用总数减去即可,即 \(tot-res\)

但是我们发现如果对于每个点求出经过它的链个数再相加肯定会重复算,所以这个时候我们考虑容斥。我们钦定 \(h_i\) 为在 \(k\) 个点中第一个经过的点是 \(c_i\) 的链的个数。
为了求出 \(h_i\)\(res\),我们要预处理三个值。我们将入度为 \(0\) 的点集(即起点)设为 \(S\),出度为 \(0\) 的点集 \(T\)(即终点)。
\(d_{i,j}\) 表示从 \(i\)\(j\) 的路径个数,\(f_i\) 为起点集 \(S\) 中的点到 \(i\) 的路径总数,\(g_i\)\(i\) 到任意终点集 \(T\) 中的点的路径总数。自然地,这些都可以通过拓扑一遍求出来。这个时候我们就可以求出 \(h_i\)\(tot\)\(res\) 了。
\(h_i=f_{c_i}-\sum_{j<i} h_j\times d_{c_j,c_i}\)\(res=\sum h_i\times g_{c_i}\)\(tot=\sum_{s∈S} g_s=\sum_{t∈T}f_t\)

Code:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int n, m, deg[N], d[N][N], rk[N], f[N], g[N], c[20], h[20];
bool S[N], T[N];
vector<int> G[N];
int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) S[i] = T[i] = 1;
	for(int i = 1; i <= m; i++) {
		int u, v; scanf("%d%d", &u, &v);
		G[u].push_back(v);
		S[v] = T[u] = 0;
		deg[v]++;
	}
	queue<int> q;
	for(int i = 1; i <= n; i++) {
		d[i][i] = 1;
		if(!deg[i]) q.push(i); 
	}
	int rank = 0;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		rk[u] = ++rank;
		for(auto v : G[u]) {
			for(int j = 1; j <= n; j++) d[j][v] = (d[j][v] + d[j][u]) % mod;
			if(!--deg[v]) q.push(v);
		}
	}
	int tot = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			if(S[j]) f[i] = (f[i] + d[j][i]) % mod;
			if(T[j]) g[i] = (g[i] + d[i][j]) % mod;
		}
		if(S[i]) tot = (tot + g[i]) % mod;
	}
	int Q; scanf("%d", &Q);
	while(Q--) {
		int k; scanf("%d", &k);
		for(int i = 1; i <= k; i++) scanf("%d", &c[i]);
		sort(c + 1, c + 1 + k, [&](int x, int y) {return rk[x] < rk[y];});
		int res = 0;
		for(int i = 1; i <= k; i++) {
			h[i] = f[c[i]];
			for(int j = 1; j < i; j++) {
				h[i] = (h[i] - 1ll * h[j] * d[c[j]][c[i]] % mod + mod) % mod;
			}
			res = (res + 1ll * h[i] * g[c[i]] % mod) % mod;
		}
		printf("%d\n", (tot - res + mod) % mod);
	}
	return 0;
}
posted @ 2026-07-26 11:15  OIerYang  阅读(4)  评论(0)    收藏  举报