ACM:每日学习 状压dp

发布时间:2024年01月16日

状压dp:

状压dp是对一般dp的改进:

//对于判断多种物品的取法,开多维数组比较麻烦,也不好开,使用二进制来表示物品的取与否。

//使用二进制的话,位运算就更能省时间了,而且更会节省空空间,敲数组也比较好敲,唯一比较难的就是位运算真是费大脑。一定要熟练的运用位运算,建议看看这个。位运算全面总结,关于位运算看这篇就够了-CSDN博客

状压dp算法的前提:

1.看看是不是有多个要取的数(不一定是多个物品,可能是一个数的每位数要取与否)一定要记录当前下标的状态和上一个节点所取的点,并且记录二进制的点,个数就是2^n(n为要取的物品),所以状压dp大部分是二维数组。

2.再者计算一下时间复杂度,看看n是不是在20左右(因为2^20为100w)要是再有多重循环的话,差不多就10^8了,正好到了计算机1s运算量了。

3.dp的基本特征吧,前面取得的会影响后面要取的。

接下来实战一下,毕竟实战比理论更容易记忆。

例题1:吃奶酪 - 洛谷

这个题目的话是要你求要吃奶酪的最短距离,其中包括n个点这就是多个物品的情况。这个题目的话题意还是比较好理解的,你要做的就是记录上一个点的位置和这个点的位置,循环求这个点到上个点的最短距离,但是你要求的是全局,而不只是单独求一个最短的距离,那么就是dp啦,从多种情况中取得最短的距离,就要最后二进制全部取1之后再循环一遍for(int i;i<=n;i++)求得ans最小。

首先把大体的框架想了一遍然后开始想想如何实现dp了。其实dp就是模板,重要的是二进制的位运算记录。for(int i=0;i<(1<<(n+1));i++)最重要的一步啊。

再者就是要想想如何简便地求距离,我偷学了一招啊,PDD(pair<double,double)(因为是距离,再者点有是实数,得用double,这不是玩梗),用pair存点非常的好用,再随便写个函数来计算就行了。

代码实现:

//洛谷:吃奶酪
//二维的?分别存储奶酪状态和落脚点
//一共有几块dp(2^n-1)
#include <bits/stdc++.h>
using namespace std;
const int N = 17;
#define PDD pair<double, double>
PDD a[N];
double dp[1 << N][N];
// 计算距离
double dis(PDD l, PDD r){
    double dx = l.first - r.first;
    double dy = l.second - r.second;
    return sqrt(dx * dx + dy * dy);
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int n,i, j, k, g;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i].first >> a[i].second;
    memset(dp, 127, sizeof dp);
    dp[1][0] = 0;
    for (i = 0; i < (1 << (n + 1)); i++){
        if (i & 1)
            for (j = 1; j <= n; j++){
                if (i & (1 << j)){
                    k = i ^ (1 << j);
                    for (g = 0; g <= n; g++)
                        if (k & (1 << g))
                            dp[i][j] = min(dp[i][j], dp[k][g] + dis(a[j], a[g]));
                }
            }
    }
    double ans = 1000000000;
//或者可以改成 db ans=-1,下面写if(ans=-1||ans>dp[(1<<(n+1))-1][i])ans=dp[(1<<(n+1))-1][i];
    for (int i = 1; i <= n; i++){
        ans = min(ans, dp[(1 << (n + 1)) - 1][i]);
    }
    printf("%.2lf\n", ans);
}

接下来我本来想写那个非常经典的题目的,但是由于非常复杂,我还没有完全明白,所以再来个别的例题吧。

例题2:[蓝桥杯 2019 省 A] 糖果 - 洛谷

一看到折磨多的糖果种类和要求的最小值就一定是状压dp啊。

这一题目也是经典啊,对于未给定每包的糖果数量我们可以先将每包糖果压缩为一个二进制的数,再一一地去用for(int j=0;j<(1<<m);j++)来一步步的加糖果袋dp[j|a[i]]就是当前的糖果状态所需要的糖果数量,而dp[(1<<m)-1]是最终2^m-1的全部糖果的状态,此时判断dp数组是否等于设的初值来判断是否能吃到全部的糖果。

代码实现:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=20;
int a[1<<N]={0};
int dp[1<<N];
int n,m,k;//n糖果包数、m糖果种类、k每包糖果的糖果数
signed main(){
    scanf("%d%d%d",&n,&m,&k);
    int num=(1<<m);
    memset(dp,0x3f3f3f3f,sizeof dp);//划分界限
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
           int t;
           scanf("%d",&t);
           a[i]=a[i]|(1<<(t-1));//将每包的糖果压缩
        }
        dp[a[i]]=1;
    }
    for(int j=0;j<num;j++)
       for(int i=1;i<=n;i++){
        // dp[j|a[i]]=((dp[j]|a[i])<dp[j]+1)?dp[j|a[i]]:dp[j]+1;
        //条件运算符?:表达式1true结果为表达式2,false结果为表达式3
        dp[j|a[i]]=min(dp[j|a[i]],dp[j]+1);//j|a[i]是用来加糖果的(|同0则0)
       }
    if(dp[num-1]==0x3f3f3f3f)printf("-1");
    else printf("%d\n",dp[num-1]);
}

以上就是比较典型的题目了,还有一种就是给你一个固定的大区域分成几个固定的小区域求分法。这种题型等之后学会在看看能不能发一下。

那就这样吧。~

文章来源:https://blog.csdn.net/northheng127/article/details/135617649
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。