状态压缩
状态压缩dp
AcWing 1064. 小国王【线性状压DP+滚动数组优化+目标状态优化】
这段代码wa了 数据是3/11,暂时找不到bug
1h后:发现了,std::vector<int> state_trans[1 << N];
而不是std::vector<int> state_trans[N];
#include <iostream>
#include <vector>
const int N = 11;
typedef long long LL;
//int f[N][N * N][1 << N]; 结果爆int了
LL f[N][N * N][1 << N];
int cnt[1 << N];
std::vector<int> legal_state;
std::vector<int> state_trans[1 << N];
int n, m;
bool check(int state)
{
return !(state & state << 1);//左移,右移都一样
}
int count(int state)
{
int res = 0;
for (int i = 0; i < n; ++ i)
{
// if (state >> i & 1) res ++ ;
res += state >> i & 1;
}
return res;
}
void solve()
{
std::cin >> n >> m;
//预处理合法状态(主要是在当前行,相邻的国王不能互相攻击)
//枚举所有状态
for (int i = 0; i < 1 << n; ++ i)
{
if (check(i))
{
legal_state.push_back(i);
cnt[i] = count(i);
}
}
//预处理合法的状态转移(主要是第i - 1行和第i行,上下两行的国王不能攻击)
for (auto cur_state : legal_state)
for (auto to_state : legal_state)
{
if (!(cur_state & to_state) && check(cur_state | to_state))
state_trans[cur_state].push_back(to_state);
}
//dp
f[0][0][0] = 1;//fijk表示前i行已经放了j个国王 且 当前行的状态为k的方案数
//枚举所有状态
for (int i = 1; i <= n; ++ i)//枚举i
for (int j = 0; j <= m; ++ j)//枚举j
for (auto state : legal_state)//枚举第i行的国王的状态
for(auto pre_state : state_trans[state])//枚举可以转移到第i行的合法放置国王的状态,之前预处理过,用于状态转移
{
int k = cnt[state];
if (j - k >= 0)
{
f[i][j][state] += f[i - 1][j - k][pre_state];
}
}
//收集结果
LL res = 0;
for (auto state : legal_state)//枚举第n行的合法状态
{
res += f[n][m][state];
}
//y总的优化方法:事实上可以N 开到 12,然后直接输出f[n + 1][m][0]
std::cout << res;
return ;
}
int main()
{
solve();
return 0;
}
AcWing 327. 玉米田【线性状压DP+滚动数组优化+目标状态优化】
不知道为什么wa了,样例都过不了,以后再看吧
#include <iostream>
#include <vector>
const int N = 13 + 1, mod = 1e8;
int n, m;
int g[N];
int f[N][1 << N];//已经种植了i行 且 第i行的状态为k的种植方法数
std::vector<int> legal_state;
std::vector<int> state_trans[1 << N];
bool check(int state)
{
return !(state & state << 1);
}
void solve()
{
std::cin >> n >> m;
//用状态压缩建图,我也是第一次见
for (int i = 1; i <= n; ++ i)
{
for (int j = 0; j < m; ++ j)//枚举状态的第0~m - 1位
{
int k;
std::cin >> k, g[i] |= !(k << j);
}
}
//预处理合法状态(主要是在同一行是否有相邻的状态)
for (int i = 0; i < 1 << n; ++ i)
{
if (check(i))
{
legal_state.push_back(i);
}
}
//预处理合法的相邻行的状态
for (auto cur_state : legal_state)
for (auto last_state : legal_state)
{
if (!(cur_state & last_state)) state_trans[cur_state].push_back(last_state);
}
//dp
f[0][0] = 1;
for (int i = 1; i <= n + 1; ++ i)
{
for (auto state : legal_state)
{
if (!(g[i] & state))//如果当前状态满足图的要求
{
for (auto last_state : state_trans[state])
{
f[i][state] = (f[i][state] + f[i - 1][last_state]) % mod;
}
}
}
}
//收集结果
// int res = 0;
// for (auto state : legal_state)
// {
// if (!(g[n] & state)) res += f[n][state];
// }
// std::cout << res;
std::cout << f[n + 1][0] % mod;
return ;
}
int main()
{
solve();
return 0;
}
AcWing 292. 炮兵阵地 【线性状压DP+常规优化+转移优化】
debug不出来,没ac,下面这是错误代码
#include <iostream>
#include <vector>
#include <algorithm>
const int N = 110, M = 12;
std::vector<int> legal_state;
std::vector<int> state_trans[1 << M];
int g[N];
int f[2][1 << M][1 << M];//在第i层状态位p1,第i - 1层状态为p2的时候前i层最多可以放多少炮兵
//本题不用滚动数组优化会爆内存
int cnt[1 << M];
int n, m;
bool check(int state)
{
return !(state & state >> 1 || state &state >> 2);
}
// int count(int state)
// {
// int res = 0;
// for (int i = 0; i < m; ++ i)
// {
// res += state >> i;
// }
// return res;
// }
int count(int st)
{
int res = 0;
while (st) res += st & 1, st >>= 1;
return res;
}
void solve()
{
std::cin >> n >> m;
for (int i = 1, j = 0; i <= n; ++ i, j = 0)//for (int j = 0, char c; j && std::cin >> c; ++ j)这样会报错,所以放在上一层了
for (char c; j < m && std::cin >> c; ++ j)//压行,帅
g[i] += (c == 'H') << j;//或者g[i] |= (c == 'H') << j;
//预处理合法状态(行)
for (int i = 0; i < 1 << m; ++ i)
{
if (check(i))
{
legal_state.push_back(i);
cnt[i] = count(i);
}
}
//预处理合法的状态转移(不同行之间的)
for (auto cur_state : legal_state)
for (auto last_state : legal_state)
{
if (!cur_state & last_state) state_trans[cur_state].push_back(last_state);
}
//dp
for (int i = 1; i <= n + 2; ++ i)
{
for (auto state : legal_state)//枚举第i层状态
if (!g[i] & state)
for (auto p1 : state_trans[state])//枚举基于第i层状态的第i-1层的合法状态
for (auto p2 : state_trans[p1])//枚举基于第i - 1层状态的第i - 2层的合法状态
if (!state & p2)
f[i & 1][state][p1] = std::max(f[i & 1][state][p1], f[(i - 1) & 1][p1][p2] + cnt[state]);
}
//output
int res = 0;
for(int i = 1; i <= n; ++ i)
for (auto state : legal_state)
for (auto p1 : state_trans[state])
res = std::max(f[i & 1][state][p1], res );
std::cout << res << std::endl;
std::cout << f[(n + 2) & 1][0][0];//目标状态优化
}
int main()
{
solve();
return 0;
}
AcWing 524. 愤怒的小鸟[状压DP+重复覆盖问题
]
我注释的那段不知道为啥一直error,明明是一样的
#include <iostream>
#include <cmath>
#include <cstring>
#define x first
#define y second
typedef std::pair<double, double> PDD;
const int N = 20;
const double eps = 1e-8;
PDD ver[N];
int path[N][N];//点a和点b构成的抛物线可以经过多少个点的状态 |后的值(我觉得状态压缩最精髓的地方就在这题,可以用|或+将几个点的状态用一个值去体现)
int f[1 << N];//覆盖状态(和)为state的猪最少需要多少条不同的抛物线
int n, m;
int cmp_lf(double a, double b)
{
if (fabs(a - b) < eps) return 0;
else if (a > b) return 1;
else return -1;
}
void solve()
{
//多组数据需要初始化一下初始化
memset(path, 0, sizeof path);
memset(f, 0x3f, sizeof f);
//input
std::cin >> n >> m;
for (int i = 0; i < n; ++ i)
{
std::cin >> ver[i].x >> ver[i].y;
}
//预处理各个点可以经过的点
for (int i = 0; i < n; ++ i)
{
path[i][i] = 1 << i;//其实这里写+=更能体现状态压缩的精髓,不过没必要
for (int j = 0; j < n; ++ j)
{
double x1 = ver[i].x, y1 = ver[i].y;
double x2 = ver[j].x, y2 = ver[j].y;
if (!cmp_lf(x1, x2)) continue;
double a = (y1 / x1 - y2 / x2) / (x1 - x2);
double b = (y1 / x1) - a * x1;
if (cmp_lf(a, 0.0) >= 0) continue; //我们需要的抛物线必须开口是向下的
//枚举可以被抛物线y=ax2+bx穿过的点
for (int k = 0; k < n; ++ k)
{
double x = ver[k].x, y = ver[k].y;
if (!cmp_lf(y, a * x * x + b * x)) path[i][j] += 1 << k;//path[i][j] += 1 << k也可以
}
}
}
//dp
f[0] = 0;//覆盖状态为000...n个0状态的猪需要0条抛物线
//枚举所有的覆盖状态(这里的覆盖状态是指很多头猪的状态叠加起来的,而不是单独的一头猪的状态)
// for (int cur_st = 0; cur_st + 1 < 1 << n; ++ cur_st)//这里不用枚举 1 << n - 1,枚举到1 << n - 2即可
// {
// int uncover = -1;//记录没有被覆盖的猪是二进制n位中的哪一位
// for (int i = 0; i < n; ++ i)
// {
// if (!(cur_st >> i & 1))
// {
// uncover = i;
// break;
// }
// }
// for (int i = 0; i < n; ++ i)//再找一个点,使得二者可以构成一条抛物线
// {
// int ne_st = path[uncover][i]|cur_st;//ne_st表示当前选取的两个点和之前已经覆盖的所有点构成的状态,用一个二进制表示当前一共覆盖了哪些
// f[ne_st] = std::min(f[cur_st] + 1, f[ne_st]);
// }
// }
for (int cur_st = 0; cur_st + 1 < 1 << n; ++ cur_st)
{
int t = -1;
for (int i = 0; i < n; ++ i)
if (!(cur_st >> i & 1))
{
t = i;
break;
}
for (int i = 0; i < n; ++ i)
{
int ne_st = path[t][i] | cur_st;
f[ne_st] = std::min(f[ne_st], f[cur_st] + 1);
}
}
//output
std::cout << f[(1 << n) - 1] << std::endl;
}
int main()
{
int T;
std::cin >> T;
while (T -- )
{
solve();
}
return 0;
}
AcWing 529. 宝藏【状压DP+最小生成树】
这题的最小生成树不是广义上的最小生成树。
关于下面这段代码更详细的注释可以看这篇博客
下面这段代码会wa,但是我找不出来哪里不对现在。
#include <iostream>
#include <cstring>
const int N = 15, M = 1 << N, INF = 0x3f3f3f3f;
int f[M][N];//在第N层覆盖了状态为M的点的集合时 所花费的最小的工程总代价(我们可以把点的的集合当作一个联通块)
int g[M];//数组的值为状态为M的点的集合可以经过一次拓展后覆盖的点的集合
int dist[N][N];//将图存储下来
int n, m;
void solve()
{
//初始化
memset(dist, 0x3f, sizeof dist);
for (int i = 0; i < n; ++ i) dist[i][i] = 0;
//input
std::cin >> n >> m;
for (int i = 0; i < m; ++ i)
{
int a, b, w;
std::cin >> a >> b >> w;
a --, b --;//下标从0开始
dist[a][b] = dist[b][a] = std::min(dist[a][b], w);
}
//预处理
//处理每个状态中的点可一步到达的所有点
for (int i = 0; i < 1 << n; ++ i)
{
for (int j = 0; j < n; ++ j)
{
if (i >> j & 1)
{
for (int k = 0; k < n; ++ k)
{
if (dist[j][k] != INF) g[i] |= 1 << k;
}
}
}
}
//dp
//dp初始化
memset(f, 0x3f, sizeof f);
for (int i = 0; i < n; ++ i) f[1 << i][0] = 0;
for (int i = 0; i < 1 << n; ++ i)//枚举当前状态
{
for (int j = (i - 1) & i; j; j = (j - 1) & i)//枚举当前状态的子集(&i的原因是因为是保证j是i的子集)
{
if ((g[j] & i) == i)//如果可以从j状态一步到达i
{
int remain = i ^ j;//找出j一步到达i所需要经过的宝藏点的集合
int cost = 0;
for (int k = 0; k < n; ++ k)//找出从j集合到i集合经过的宝藏点的权值和
{
if (remain >> k & 1)
{
int t = INF;
for (int u = 0; u < n; ++ u)//找出j集合当前已有的宝藏点,通过这些宝藏点扩展到k点
{
if (j >> u & 1) t = std::min(t, dist[k][u]);
}
cost += t;
}
}
for (int k = 1; k < n; ++ k)//k从1开始枚举,不是0
{
f[i][k] = std::min(f[i][k], f[j][k - 1] + cost * k);//cost是这条路的长度,k是经过的宝藏点的数量
}
}
}
}
int ans = INF;
for (int k = 1; k < n; ++ k)
{
ans = std::min(ans, f[(1 << n) - 1][k]);
}
std::cout << ans ;
return ;
}
int main()
{
solve();
return 0;
}