P7859 2 的 n 次方做法
typerxiaozhu · · 题解
前言:本题
https://www.luogu.com.cn/problem/P7859
最傻的一种做法:二进制枚举,枚举披萨配料的放置状态再检查冲突的两个位上是否都是
时间复杂度为
#include <iostream>
using namespace std;
const int N = 405;
typedef pair<int, int> PII;
PII limit[N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> limit[i].first >> limit[i].second;
int res = 0;
for (int i = 0; i < 1 << n; i++)
{
bool flag = true;
//检查是否满足所有的限制
for (int j = 1; j <= m; j++)
{
int x = limit[j].first - 1, y = limit[j].second - 1;
if ((x != y) && (i >> x & 1) && (i >> y & 1))
{
flag = false;
break;
}
}
if (flag) res++;
}
cout << res;
return 0;
}
上述想法非常的傻,产生了大量无效的操作,例如
刷表法:一般的 DP 都使用填表法(即考虑一个状态可以由哪些状态转移过来),而刷表法是考虑一个状态可以转移到哪些状态去,在某些问题中更加符合人的思维
其实对于这种能否构成的问题,最好的办法就是刷表法(考虑计算机问题时要考虑增量,一步到另一步之间的变化,也就是动态规划中的 最优子结构),对于一个已知的合法状态
#include <iostream>
using namespace std;
const int N = 25;
int limit[N]; //是否有冲突
bool dp[1 << 20];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
x--, y--;
limit[x] |= 1 << y;
limit[y] |= 1 << x;
}
dp[0] = 1;
for (int i = 0; i < 1 << n; i++)
{
if (dp[i]) //若状态合法进行刷表
{
for (int j = 0; j < n; j++) //枚举哪一位放 1
if (!(i >> j & 1) && !(i & limit[j]))
dp[i | 1 << j] = 1;
}
}
int res = 0;
for (int i = 0; i < 1 << n; i++) res += (dp[i] == 1);
cout << res;
}
我们可以借鉴刷表法的思路换成填表法,枚举最后一次放的是哪一位,另外考虑一个问题,假如
#include <iostream>
using namespace std;
const int N = 25, M = 1 << 20;
int dp[M], mark[N];
int lowbit(int x)
{
return x & (-x);
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
a--, b--;
mark[a] |= 1 << b;
mark[b] |= 1 << a;
}
int ans = 0;
dp[0] = 1;
for (int i = 1; i < 1 << n; i++)
{
//只枚举最低位
int k = __builtin_ctz(i);
int j = 1 << k;
if (dp[i - j] && !(mark[k] & (i - j))) dp[i] = 1;
}
for (int i = 0; i < 1 << n; i++) ans += (dp[i] == 1);
cout << ans;
return 0;
}