CF11D-A Simple Task(状态压缩)

CF11D-A Simple Task

思路:
数据规模不大,显然可采用状态压缩先构建所有的链,然后将首尾相连构成环进行计数即可。

主要思考如下两个问题:

  1. 如何不重复地构建链
    每条链一定存在一个编号最小的点,我们保持每条链的其中一个端点是编号最小的点即可。具体做法,就是在枚举状态时,绝不连接终点为该状态最小编号的边。

  2. 如何构建环
    每次连接时,枚举当前状态的每一个点,与状态中最小编号端点直接连接,均构成一个环,进行计数即可。
    这里还要注意两个问题:
    (1)每个环从最小端点朝两个方向都被计数一次,最终要除以 2。
    (2)每条对称边也被计数,但只被计 1 次。

代码采用两种方式:
第一种:我为人人,用不在状态里的点去更新当前枚举的状态。这样可以把代码写得比较短,集中在一个循环里完成两种操作。

#include 
#include 
using namespace std;
const int N = 1<<19 | 7;
using LL = long long;
LL f[N][22];
bool g[22][22];
unordered_map<int, int> ump;
int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		ump[1<<i-1] = i, f[1<<i-1][i] = 1;
	for (int i = 1, u, v; i <= m; ++i) {
		scanf("%d%d", &u, &v);
		g[u][v] = g[v][u] = true;
	}
	LL ans = 0;
	for (int mask = 1; mask < (1<<n); ++mask) {
		int mn = ump[mask & -mask];
		for (int bi = mask; bi; bi &= bi-1) {
			int t = bi & -bi, i = ump[t];
			for (int j = 1; j <= n; ++j) {
				if (j<mn || !g[i][j]) continue;
				if (!(mask>>j-1 & 1))// j不在mask中
					f[mask|1<<j-1][j] += f[mask][i];
				else if (j == mn)// j恰为最小编号端点
					ans += f[mask][i];
			}
		}
	}
	printf("%lld", ans-m>>1);
}

第二种:人人为我,用当前状态里的点去更新当前状态。第一次循环保证至少该状态要有 2 个点,第二次循环保证当前链至少有 3 个点,这样答案就不需要减去 m m m 了。

#include 
#include 
using namespace std;
const int N = 1<<19 | 7;
using LL = long long;
LL f[N][22];
bool g[22][22];
unordered_map<int, int> ump;
int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		ump[1<<i-1] = i, f[1<<i-1][i] = 1;
	for (int i = 1, u, v; i <= m; ++i) {
		scanf("%d%d", &u, &v);
		g[u][v] = g[v][u] = true;
	}
	LL ans = 0;
	for (int mask = 3; mask < (1<<n); ++mask) {
		int t = (mask & -mask) ^ mask;
		if (!t) continue;
		for (int bi = t; bi; bi &= bi-1) {
			int v = bi & -bi, i = ump[v], j;
			for (int bj = mask^v; bj; bj &= bj-1) {
				t = bj & -bj, j = ump[t];
				if (!g[i][j]) continue;
				f[mask][i] += f[mask^v][j];
			}
		}
	}
	for (int mask = 7; mask < (1<<n); ++mask) {
		int t = mask & -mask, i = ump[t], ct = 0;
		for (int bj = mask; bj; bj &= bj-1) ++ct;
		if (ct < 3) continue;
		for (int bj = t ^ mask; bj; bj &= bj-1) {
			int j = ump[bj&-bj];
			if (!g[i][j]) continue;
			ans += f[mask][j];
		}
	}
	printf("%lld", ans>>1);
}

你可能感兴趣的:(CodeForces,CF题解)