CF1425B Blue and Red of Our Faculty!

CF1425B Blue and Red of Our Faculty!

题目来源:Codeforces, 2020 ICPC, COMPFEST 12, Indonesia Multi-Provincial Contest (Unrated, Online Mirror, ICPC Rules, Teams Preferred), CF1425B Blue and Red of Our Faculty!

题目大意

题目链接

给定一张 \(n\) 个点 \(m\) 条边的无向连通图,保证无自环,保证除节点 \(1\) 外每个点的度数都为 \(2\)

有两人 Red 和 Blue 同时从节点 \(1\) 出发。初始时所有边都是灰色。Red 每经过一条边就会将它染成红色,Blue 每经过一条边就会将它染成蓝色。每轮中,每人会选择一条与当前所在节点相连的、灰色的边,并走到边的另一端。同一轮里两人选择的边不能相同

当无法进行下一轮时,整个过程停止。问两人能走出多少种不同的最终局面,答案对 \(10^9 + 7\) 取模。两个最终局面不同当且仅当存在一条边,在最终局面下颜色不同。

数据范围:\(1\leq n\leq 2000\)\(1\leq m\leq 2n\)

本题题解

根据“保证除节点 \(1\) 外每个点的度数都为 \(2\)”,可以发现整张图呈一个花瓣状。也就是有若干个环,节点 \(1\) 是它们的公共点。我们可以用一个序列(或者说可重集)\(\{a_1,a_2,\dots,a_k\}\) 来描述整张图,其中 \(a_i\) 表示第 \(i\) 个环的大小,\(k\) 是环的数量。

考虑结束时两人的位置。可以分为三种情况:

  1. 两人都在节点 \(1\)。此时他们一定经过了所有环(图中没有灰边),且步数相同。方案数就相当于把 \(a\) 划分为两个集合,使他们和相等的方案数。
  2. 两人都在某个环的某个节点上(不含节点 \(1\))。设该环的大小为 \(l\),在环上 Red 走了 \(x\) (\(1\leq x < l\)) 步,则 Blue 走了 \(l - x\) 步。两人的步数差为 \((l - x) - x = l - 2x\)。问题相当于从 \(a\) 除当前环外的部分里(即 \(a\setminus\{l\}\)),选出两个互不相交的子集,使他们的和之差为 \(l - 2x\)
  3. 两人都在某个环上,且相距为 \(1\)(隔着一条边)。此时又可以分为两种情况:
    • 两人都不在节点 \(1\) 上(严格位于环上)。这与情况 2 类似,只不过此时两人的步数差为 \(l - 1 - 2x\),且 \(1\leq x\leq l - 2\)
    • 其中一人在节点 \(1\) 上。此时 \(x = 0\)\(x = l - 1\)。除了两人的步数差为 \(l - 1 - 2x\) 外,还需要保证其他环都被走过。也就是选出的两个子集的并为全集(全集,指:\(a\) 除去一个 \(l\) 外的其他所有元素,即 \(a\setminus\{l\}\))。

这三种情况,都可以用同一个 DP 来计算。

先枚举 \(l\),从 \(a\) 中暂时删掉一个 \(l\)(如果是计算情况 \(1\),则不必删除任何元素)。

\(\text{dp}(i,j,\text{flag})\) 表示考虑了前 \(i\) 个环,当前 【Red 走的步数】\(-\)【Blue 走的步数】等于 \(j\)\(\text{flag}\) 是个 \(0/1\) 值,表示有没有两人都没走过的环。注意 \(j\) 这里,把“朴素地记录两人步数分别是多少”,变成“记录两者之差”,是个设计状态的小套路(CSP2019 d2t1 也有用到)。这么做是因为我们最终只关心它们的差。

转移就考虑当前环,是被 Red 走,还是被 Blue 走,还是两人都不走:

\[\begin{cases} \text{dp}(i - 1, j, \text{flag}) \to \text{dp}(i, j + a_i, \text{flag})\\ \text{dp}(i - 1, j, \text{flag}) \to \text{dp}(i, j - a_i, \text{flag})\\ \text{dp}(i - 1, j, \text{flag}) \to \text{dp}(i, j, 0) \end{cases} \]

情况 1 的答案是 \(\text{dp}(k, 0, 1)\)\(k\) 是环的数量。

情况 2 的答案是 \(\displaystyle 2\times \sum_{x = 1}^{l - 1}(\text{dp}(k - 1, l - 2x, 0) + \text{dp}(k - 1, l - 2x, 1))\)。其中乘以 \(2\) 是因为最后一个环,Red 可以从两个方向(顺时针 / 逆时针)之一开始走。

情况 3 的答案是 \(\displaystyle 2\times \left(\sum_{x = 1}^{l - 2}\text{dp}(k - 1, l - 1 - 2x,0) + \sum_{x = 0}^{l - 1}\text{dp}(k - 1, l - 1 - 2x,1)\right)\)

一次 DP 的时间复杂度是 \(\mathcal{O}(n^2)\)。但是外层要枚举 \(l\)。虽然环的数量可能高达 \(\mathcal{O}(n)\) 个,但由于 \(\sum\text{环长} = \mathcal{O}(n)\),所以不同的环长 \(l\) 只有 \(\mathcal{O}(\sqrt{n})\) 种。暴力枚举 \(l\) 再做 DP 的总时间复杂度 \(\mathcal{O}(n^2\sqrt{n})\),可以通过本题。另有一些优化方法:

  • 优化方法1:这是一个缺一背包的模型,所以可以用分治来优化,做到 \(\mathcal{O}(n^2\log n)\)
  • 优化方法2:仍然暴力枚举 \(l\)。不对可重集 \(a\) 做 DP,而是把 \(a\) 缩成若干个二元组 \((\text{元素},\text{出现次数})\),然后类似于做多重背包。单次 DP 的时间复杂度是 \(\mathcal{O}(n\sqrt{n})\),总时间复杂度 \(\mathcal{O}(n^2)\)

参考代码

时间复杂度 \(\mathcal{O}(n^2\sqrt{n})\)

// problem: CF1425B
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }


const int MAXN = 2000;
const int MOD = 1e9 + 7;
inline int mod1(int x) { return x < MOD ? x : x - MOD; }
inline int mod2(int x) { return x < 0 ? x + MOD : x; }
inline void add(int &x, int y) { x = mod1(x + y); }
inline void sub(int &x, int y) { x = mod2(x - y); }


int n, m;
vector<int> G[MAXN + 5];

int cir[MAXN + 5];
int a[MAXN + 5], cnt;

int dp[MAXN + 5][MAXN * 4 + 5][2];
int calc_dp(int len) {
	// 求两人最终在一个长度为 len 的环上相遇的方案数
	// 特别地, len = 0 表示把所有环都完整地走一遍
	
	cnt = 0;
	int sum = 0;
	for (int i = 2; i <= n; ++i) {
		for (int j = 1; j <= cir[i] - (i == len); ++j) {
			a[++cnt] = i;
			sum += i;
		}
	}
	
	for (int i = 0; i <= cnt; ++i) {
		for (int j = -sum; j <= sum; ++j) {
			for (int k = 0; k <= 1; ++k) {
				dp[i][j + sum][k] = 0;
			}
		}
	}
	dp[0][0 + sum][1] = 1;
	for (int i = 1; i <= cnt; ++i) {
		for (int j = -sum; j <= sum; ++j) {
			for (int k = 0; k <= 1; ++k) if (dp[i - 1][j + sum][k]) {
				// red
				assert(j + a[i] <= sum);
				add(dp[i][j + a[i] + sum][k], dp[i - 1][j + sum][k]);
				// blue
				assert(j - a[i] >= -sum);
				add(dp[i][j - a[i] + sum][k], dp[i - 1][j + sum][k]);
				// neither
				add(dp[i][j + sum][0], dp[i - 1][j + sum][k]);
			}
		}
	}
	
	if (!len) {
		return dp[cnt][0 + sum][1];
	}
	
	int res = 0;
	for (int i = 1; i < len; ++i) {
		// red 走的
		int dif = (len - i) - i;
		if (abs(dif) > sum)
			continue;
		add(res, dp[cnt][dif + sum][0]);
		add(res, dp[cnt][dif + sum][1]);
	}
	
	for (int i = 0; i < len; ++i) {
		int dif = (len - i - 1) - i;
		if (abs(dif) > sum)
			continue;
		if (i != 0 && i != len - 1)
			add(res, dp[cnt][dif + sum][0]);
		add(res, dp[cnt][dif + sum][1]);
	}
	
	res = res * 2 % MOD;
	return res;
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= m; ++i) {
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	
	static bool vis[MAXN + 5];
	for (int i = 0; i < SZ(G[1]); ++i) {
		int u = G[1][i];
		if (vis[u]) {
			continue;
		}
		
		int len = 1;
		int lst = 1;
		while (u != 1) {
			len++;
			vis[u] = 1;
			int nxt_u = (G[u][0] ^ G[u][1] ^ lst);
			lst = u;
			u = nxt_u;
		}
		cir[len]++;
		// cerr << "found cir of len: " << len << endl;
	}
	
	int ans = 0;
	for (int i = 2; i <= n; ++i) {
		if (cir[i]) {
			add(ans, (ll)calc_dp(i) * cir[i] % MOD);
		}
	}
	
	add(ans, calc_dp(0));
	cout << ans << endl;
	return 0;
}
posted @ 2020-12-29 22:35  duyiblue  阅读(155)  评论(1编辑  收藏  举报