【luogu P7418】Counting Graphs P(DP)(思维)(容斥)

Counting Graphs P

题目链接:luogu P7418

题目大意

给你一个图,然后 fi,j 表示是否存在一个从 1 到 i 的路径经过的边数是 j。
然后问你能构造出来多少个图使得它的 f 函数跟给出的图的一样。
会有自环,没有重边。

思路

首先参考 Minimizing Edges P 的思路,搞出奇偶最短路。

首先还是一样,特判二分图。
那如果二分图,每一层的点数分别是 \(a_1,a_2,...\)
那我们考虑对于每两层之间算连边的方案数,然后乘起来。
那对于第 \(i\) 层和第 \(i+1\) 层,然后其实要 \(i+1\) 每个都连到,那其实对于每个点可以枚举令一行与它的连边状态,那就是 \((2^{a_i}-1)^{a_{i+1}}\)

然后我们考虑继续用上一题的思路,\(x+y\)\(y\) 坐标,\(x\)\(x\) 坐标。
然后考虑在这个上面 DP。
那往上一层的时候还好,但一层内转移是两边都要的,所以我们要多设一维,表示 \(f_{x,y,p}\) 为当前到 \((x,y)\) 这个状态,\(p\) 个点需要下一个状态连过去(这个转移要是内部的转移)。(\(s1\)\((x,y)\) 这个状态的个数,\(s2\)\((x-1,y-1)\) 这个状态的个数)

\(f_{x,y,p}=\sum\limits_{q=0}^{s1-p}g_{x,y,q}C_{s1-p}^q(2^{s2}-1)^{s1-p}\)

解释:
我们枚举的 \(q\) 是在这里两层传递 \(q\) 个。
\(C_{s1-p}^q\):在剩下不内部转移的点中选 \(q\) 个进行一层的转移。
\((2^{s2}-1)^{s1-p}\):两层之间转移的方案数,跟二分图的计算是一样的。
\(g_{x,y,q}\):表示 \(s1-q\) 个点都是由 \((x-1,y+1)\) 转移过来的方案数。

然后接着你考虑如何转移 \(g_{x,y,p}\)
考虑枚举上一个状态有多少个点需要现在这个状态把边连过去,然后枚举为 \(q\) 个,然后:(\(s1\)\((x,y)\) 这个状态的个数,\(s3\)\((x+1,y-1)\) 这个状态的个数)

\(g_{x,y,p}=C_{s1}^p\sum\limits_{q=0}^{s3}f_{x+1,y-1,q}h_{s3,q,s1-p}\)

解释:
\(C_{s1}^p\):先要选 \(p\) 个。
\(f_{x+1,y-1,q}\):上一个状态的方案数。
\(h_{s3,q,s1-p}\):表示大小为 \(s3\) 的集合跟大小为 \(s1-p\) 的集合之间连边,保证 \(s2\) 中的 \(q\) 个点和大小为 \(s1-p\) 的集合的度数至少是 \(1\),它的方案数。

不难看出又要求 \(h_{i,j,k}\),考虑用容斥,枚举 \(p\)\(j\) 中的点没有出度。

\(h_{i,j,k}=\sum\limits_{p=0}^j(-1)^pC_j^p(2^{i-p}-1)^k\)

解释:
\((-1)^p\):容斥
\(C_j^p\):在要的 \(j\) 个点中选 \(p\) 个不要。
\((2^{i-p}-1)^k\):匹配的方案数,跟二分图的搞法是一样的。

然后考虑如何统计答案,不难看出就是每一行的最后一个(\(x+1=y\) 或者就是最后一个状态)
那明显的看到这两个肯定统计方法是不一样的(一个可以内部消化,一个不行)
那如果不能内部消化,那就不能往右边连,所以贡献就是 \(f_{x,y,0}\)

那如果是 \(x+1=y\) 呢?
那我们就可以同类消化,使得任意个点要往右连都是可以的。
\((x,y)\),即 \((x,x+1)\) 的状态个数是 \(s\)

那贡献就是 \(\sum\limits_{i=0}^sT_{s,i}f_{x,y,i}\)

解释:
\(f_{x,y,i}\):就是原来状态的方案数。
\(T_{s,i}\):大小为 \(s\) 的集合中 \(i\) 个点通过同类连边消化掉的方案数。

然后考虑求 \(T_{i,j}\),也是容斥,设 \(k\) 个点的度数是 \(0\)

\(T_{i,j}=\sum\limits_{k=0}^j(-1)^kC_j^k2^{\frac{(i-k)(i-k+1)}{2}}\)

解释:
\((-1)^k\):容斥
\(C_j^k\):选 \(k\) 个度数为 \(0\)
\(2^{\frac{(i-k)(i-k+1)}{2}}\):除去 \(k\) 个点剩下点任意连边的方案数。
就是对于每一条可能的边都有选或者不选,点数是 \(i-k=x\),可能的边就是 \(\dfrac{x(x-1)}{2}\)\(\dfrac{(i-k)(i-k-1)}{2}\),然后每条边都可以选或不选就是 \(2\) 的那么多次方了。

然后自此,你就可以搞出来了!!!

至于 \(T,h\) 你可以直接预处理,也可以开个记忆化,要用再算,然后 \(f,g\) 就是每次询问都会变,记得初始化。
然后你可以预处理出来阶乘(以及它的逆元),组合数,\(2^x\)\(x\leqslant n^2\)),以及 \((2^{x}-1)^y\)\(x,y\leqslant n\)

然后就可以搞啦!

代码

#include<map>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define mo 1000000007
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

const int N = 205;
const int M = 80005;
struct node {
	int to, nxt;
}e[M];
int T, n, m, x, y, le[N], KK;
int cunt[N];
ll ans, f[N][N][N >> 1], g[N][N][N >> 1], t[N][N], h[N][N][N];
ll jc[N], cff[N][N], inv[N], cf[M], s1, s2, s3, re;
bool gogo;

struct st {
	int x, y;
}tp[N];
int nm[N][N];
int tpn, tpx, tpy;

struct ztzt {
	int dis, now;
};
bool operator <(ztzt x, ztzt y) {
	return x.dis > y.dis;
}
priority_queue <ztzt> q;
int dis[N << 1];
bool in[N << 1];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

void dij() {//跑出奇偶最短路
	for (int i = 0; i <= 2 * n; i++)
		in[i] = 0, dis[i] = INF;
	dis[1] = 0; q.push((ztzt){0, 1});
	while (!q.empty()) {
		int now = q.top().now;
		q.pop();
		if (in[now]) continue;
		in[now] = 1;
		for (int i = le[now]; i; i = e[i].nxt)
			if (!in[e[i].to] && dis[e[i].to] > dis[now] + 1) {
				dis[e[i].to] = dis[now] + 1;
				q.push((ztzt){dis[e[i].to], e[i].to});
			}
	}
}

bool cmp1(st x, st y) {
	if (x.x + x.y != y.x + y.y) return x.x + x.y < y.x + y.y;
	return x.x < y.x;
}

ll ksm(ll x, ll y) {
	ll re = 1;
	while (y) {
		if (y & 1) re = (re * x) % mo;
		x = (x * x) % mo;
		y >>= 1;
	}
	return re;
}

ll C(int n, int m) {
	if (n < m) return 0ll;
	return jc[n] * inv[m] % mo * inv[n - m] % mo; 
}

ll H(int i, int j, int k) {
	if (h[i][j][k] != -1) return h[i][j][k];
	h[i][j][k] = 0;
	for (int p = 0; p <= j; p++) {
		h[i][j][k] = (h[i][j][k] + ((p & 1) ? -1 : 1) * C(j, p) * cff[i - p][k] % mo) % mo;
		if (h[i][j][k] < 0) h[i][j][k] += mo;
	}
	return h[i][j][k];
}

int main() {	
	jc[0] = 1;//预处理
	for (int i = 1; i < N; i++)
		jc[i] = (jc[i - 1] * i) % mo;
	inv[N - 1] = ksm(jc[N - 1], mo - 2);
	for (int i = N - 2; i >= 0; i--)
		inv[i] = (inv[i + 1] * (i + 1)) % mo;
	cf[0] = 1;
	for (int i = 1; i < M; i++) cf[i] = (cf[i - 1] << 1) % mo;
	for (int i = 0; i < N; i++) {
		cff[i][0] = 1;
		for (int j = 1; j < N; j++)
			cff[i][j] = (cff[i][j - 1] * (cf[i] - 1)) % mo;
	}
	
	memset(h, -1, sizeof(h));
	
	for (int i = 0; i < N; i++)
		for (int j = 0; j <= i; j++) {
			for (int k = 0; k <= j; k++) {
				t[i][j] = (t[i][j] + ((k & 1) ? -1 : 1) * C(j, k) * cf[(i - k) * (i - k + 1) / 2] % mo) % mo;
				if (t[i][j] < 0) t[i][j] += mo;
			}
		}
	
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d", &n, &m);
		
		for (int i = 0; i <= 2 * n; i++)//清空数组
			for (int j = 0; j <= 2 * n; j++)
				for (int k = 0; k <= n; k++)
					f[i][j][k] = g[i][j][k] = 0;
		KK = 0;
		for (int i = 0; i <= 2 * n; i++) le[i] = 0;
		
		for (int i = 1; i <= m; i++) {
			scanf("%d %d", &x, &y);
			add(x, y + n); add(x + n, y);
		}
		
		dij();
		
		gogo = 0;
		for (int i = 1; i <= n; i++) {
			if (dis[i] == dis[0] || dis[i + n] == dis[0]) {
				gogo = 1;
				break;
			}
		}
		ans = 1; tpn = 0;
		if (gogo) {//特判二分图
			for (int i = 0; i <= n * 2; i++) cunt[i] = 0;
			for (int i = 1; i <= n; i++) {
				if (dis[i] != dis[0]) cunt[dis[i]]++;
					else if (dis[i + n] != dis[0]) cunt[dis[i + n]]++;
			}
			for (int i = 1; i <= n * 2; i++) {
				ans = (ans * cff[cunt[i - 1]][cunt[i]]) % mo;
			}
			printf("%lld\n", ans);
		}
		else {
			for (int i = 1; i <= n; i++) {
				tpx = dis[i]; tpy = dis[i + n];
				if (tpx > tpy) swap(tpx, tpy);
				nm[tpx][tpy]++; tp[++tpn] = (st){tpx, tpy};
			}
			sort(tp + 1, tp + tpn + 1, cmp1);
			
			for (int i = 1; i <= tpn; i++) {//DP
				int st = i;
				while (i < tpn && tp[i].x == tp[i + 1].x && tp[i].y == tp[i + 1].y) i++;
				x = tp[i].x; y = tp[i].y;
				
				s1 = nm[x][y];
				if (!x || !y) s2 = 0;
					else s2 = nm[x - 1][y - 1];
				if (!x) s3 = 0;
					else s3 = nm[x - 1][y + 1];
				if (!s3) g[x][y][st == 1 ? 0 : s1] = 1;
				else {
					for (int p = 0; p <= s1; p++) {
						for (int q = 0; q <= s3; q++)
							g[x][y][p] = (g[x][y][p] + f[x - 1][y + 1][q] * H(s3, q, s1 - p) % mo) % mo;
						g[x][y][p] = (g[x][y][p] * C(s1, p)) % mo;
					}
				}
				for (int p = 0; p <= s1; p++) {
					for (int q = 0; q <= s1 - p; q++)
						f[x][y][p] = (f[x][y][p] + C(s1 - q, p) * g[x][y][q] % mo * cff[s2][s1 - p] % mo) % mo;
				}
				
				if (i == tpn || tp[i + 1].x != x + 1 || tp[i + 1].y != y - 1) {//把每一层的贡献算了
					if (x + 1 == y) {
						re = 0;
						for (int p = 0; p <= s1; p++)
							re = (re + f[x][y][p] * t[s1][p] % mo) % mo;
						ans = (ans * re) % mo;
					}
					else {
						ans = (ans * f[x][y][0]) % mo;
					}
				}
			}
			
			printf("%lld\n", ans);
			
			for (int i = 1; i <= n; i++) {
				tpx = dis[i]; tpy = dis[i + n];
				if (tpx > tpy) swap(tpx, tpy);
				nm[tpx][tpy] = 0;
			}
		}
	} 
	
	return 0;
}
posted @ 2021-08-24 09:35  あおいSakura  阅读(74)  评论(0编辑  收藏  举报