把每份披萨看成一个原料子集,直接状压枚举所有 2^N 个子集并检查是否包含冲突对即可。
OJ: luogu
题目 ID: P7859
难度:普及-
标签:状态压缩枚举位运算图论
日期: 2026-06-21 05:05
题意
有 N 种原料,给出 M 对冲突原料。
如果一份披萨同时包含某对冲突原料,这份披萨就不合法。 问一共有多少种合法的披萨。
思路
先看一个递归枚举的暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 405;
int n, m;
int x[MAXM], y[MAXM];
int choose_item[25];
int ans;
void dfs_choose(int u) {
if (u == n + 1) {
for (int i = 1; i <= m; i++) {
if (choose_item[x[i]] && choose_item[y[i]]) {
return;
}
}
ans++;
return;
}
choose_item[u] = 0;
dfs_choose(u + 1);
choose_item[u] = 1;
dfs_choose(u + 1);
choose_item[u] = 0;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// brute.cpp:递归枚举每种原料选或不选,再检查是否出现冲突对。
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
}
ans = 0;
memset(choose_item, 0, sizeof(choose_item));
dfs_choose(1);
cout << ans << '\n';
return 0;
}这个暴力对每种原料做“选 / 不选”的决策,最后检查是否合法。
本题虽然看起来是指数级枚举,但数据范围只有 N<=20。
这意味着总子集数最多只有:
2^20 = 1048576
完全可以直接做。
于是把每份披萨看成一个子集:
mask的第i位为1,表示第i种原料被选中
然后检查每条冲突边 (x,y):
- 如果
x,y在这个子集中同时出现,那么这个子集不合法 - 否则它就是一种可行披萨
所以这题其实就是在统计“冲突图的独立集个数”。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 405;
int n, m;
int x[MAXM], y[MAXM];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
x[i]--;
y[i]--;
}
int ans = 0;
int total = 1 << n;
for (int mask = 0; mask < total; mask++) {
bool ok = true;
for (int i = 1; i <= m; i++) {
if ((mask & (1 << x[i])) && (mask & (1 << y[i]))) {
ok = false;
break;
}
}
if (ok) {
ans++;
}
}
cout << ans << '\n';
return 0;
}复杂度
时间复杂度
总结
这题的关键不是设计复杂算法,而是敢于根据数据范围直接选择最朴素的状压枚举。