算法提高-动态规划-状态压缩

状态压缩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;
}

猜你喜欢

转载自blog.csdn.net/chirou_/article/details/131817235
今日推荐