dp题单vjudge 8.17

z-zhi / 2024-08-18 / 原文

HDU-1024 Max Sum Plus Plus

https://acm.hdu.edu.cn/showproblem.php?pid=1024

可以想到用dp过,但是无论时间和空间都不够,然后就不会了

先写出转移方程,然后发现如果把其中一部分用其他的东西储存起来,就不需要重复寻找,直接使用就行了

3天的沉淀o( ̄┰ ̄*)ゞ

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[1000006]={0};
int b[1000006]={0};
int dp[1000006]={0};
const int inf=0x7ffffffff;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n,k;
    while(cin>>k>>n){
        for(int i=1;i<=n;++i){
            cin>>a[i];
        }
        for(int i=1;i<=n;++i){
            b[i]=dp[i]=0;
        }
        for(int i=1;i<=k;++i){
            b[i-1]=dp[i-1];
            for(int j=i;j<=n;++j){
                b[j]=max(b[j-1],dp[j]);
                dp[j]=dp[j-1]+a[j];
                dp[j]=max(dp[j],b[j-1]+a[j]);
            }
        }
        int ans=-inf;
        for(int i=k;i<=n;++i){
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }   
    return 0;
}

HDU-1047 Doing Homework

难死我了!查阅博客

开幕雷击,谢谢。


使用二进制,说实话在写这个题目的时候有乱想到,但是感觉更像是搜索因为时间复杂度就放弃,遂仔细梳理一下

一门课程完成的状态可以从未完成的状态转移过来,前一个未完成的状态是在之前得到过的最优解

和搜索有什么区别呢?当完成n门课的时候,实际上我已经把每一种顺序都跑了一次

好像回归到了dp最初的定义:把要重复使用的数据记录下来,下一次就不需要再算了

这道题的状态转移方程是好想的。


如果是直接搜索:对于一个规划好的顺序来看,假使我用solve函数去写,一定是一步步走下去的,这样就不好,但是dfs感觉应该是可以完成的,因为思维太朴素了,实际上搜索也一定不是把每一种顺序重新跑一遍,而是在当前的顺序下继续走下去而已。

算下时间复杂度,如果采取的措施是把选过的东西打上标记,不会重复搜索,那么相当于是 O(N^N) 然后 N<15。其实是可以的吧

很不喜欢老题,因为很多数据的范围都没有给好,但是不做心里又难受,要消耗好多时间,尝试写一下dfs

因为题型知道了,接下来去找几道专门的状压dp做做就好,说实话要怎么保证字典序最小是一点想法都没有,还是太弱。但是评论区说数据给的很糟,不用考虑这一点,开心!ヾ(≧▽≦*)o

尝试写下,当做是锻炼代码能力了

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int n;
map<int,string> p;
int a[20];//jie zhi
int b[20];//hao shi
int vis[20]={0};
int ans=0x7ffffffff;
vector<int> ansv;
vector<int> v;
void init(){
    ans=0x7ffffffff;
    p.clear();
    for(int i=1;i<=n;++i){
        vis[i]=0;
    }
    v.clear();
    ansv.clear();
}
void change(){
    ansv.clear();
    for(auto i:v){
        ansv.push_back(i);
    }
}
void dfs(int step,int now,int score){
    if(step==n) {
        if(score<ans){ change();ans=score; }
        return;
    }
    for(int i=1;i<=n;++i){
        if(vis[i]==1) continue;
        vis[i]=1;
        int t=now+b[i];
        int delt=t-a[i];
        if(delt<=0) delt=0;
        int ss=score+delt;
        v.push_back(i);
        dfs(step+1,t,ss);
        v.pop_back();
        vis[i]=0;
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int time;
    cin>>time;
    while(time--){
        cin>>n;
        init();
        for(int i=1;i<=n;++i){
            string s;
            cin>>s;
            p[i]=s;
            cin>>a[i]>>b[i];
        }
        dfs(0,0,0);
        cout<<ans<<endl;
        for(auto i:ansv){
            cout<<p[i]<<endl;
        }
    }
    return 0;
}

出乎意料的快速的写完了。发现时间复杂度算错数字了,时间复杂度早就超了。看来是没有理解透彻状压dp

找些简单的状压做做

突然开窍,搜索和dp的区别就是

假如我要找从 n个科目-------转移到-------k个科目

如果是搜索,则是把每一种从 0-n的情况全都延续下去,一直到k个课程

相当于如果是从 n-k有m步,从0-n 有M 步,那样搜索总共走过的步数是 m*M

而dp来说我是在跑完 M的情况下,找出最优秀的情况然后转移到 k ,总的步数是 M+m

差不多就是这样的意思吧

非常聪明!o( ^ ▽ ^ )o

确实如同一篇博客所说 : 状压dp的特征就是 : 和搜索差不多 也是一种暴力

这一篇博客我印象非常深刻,以前曾分工动态规划,但是没有看懂

然后考虑如何保证字典序

假如我是从字典序小的一方开始遍历,那样我得到的第一个合适的最小值对应的方案就是字典序最小的方案

dp实现思路:

对于k节课程的状态,是由 这k个课程分别让每一个没有完成的状态转移过来,小的遍历然后取min值,大遍历的i节课程

关于转移到 选了k的状态,用一个tem=(1<<i)然后用k&tem即可,如果不为0则是这个书对于k来说是我想选的,然后把这一位给变为0,用异或即可,得到之前的状态

过程中我需要找到每一种状态的最优解,所以就是每一种状态都需要去跑一遍,为了能充分的传递,要从1的数目最少的开始。要怎么样才能把这些数字挑出来呢?

发现其实根本就不需要把这些数字给跳出来,如果是从1开始然后一直到(1<<n)就是朴素的遍历也就能保证需要的状态在之前就已经求得过了。

从二进制位上开始考虑 需要的位数上全是1的情况的数的大小其实就已经比所有能传递到这样的状态的数字更大了,所以只要从小往大遍历就完全足够!

很多代码实现的思路上的东西就是不太好讲,曾经也抱怨为什么很多博客的作者根本就不讲思路

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int cnt=0;
int n;
map<int,string> p;
int a[100]={0};//jiezhi
int b[100]={0};//haoshi
int dp[40000]={0};
int tt[40000]={0};
vector<int> v[40000];//记录 字符串(顺序)
void change(int pre,int now,int add){
    v[now].clear();
    for(auto i:v[pre]){
        v[now].push_back(i);
    }
    v[now].push_back(add);
}
void init(){
    p.clear();
    for(int i=1;i<=(1ll<<n);++i){
        v[i].clear();
        dp[i]=0x7fffffff;
        tt[i]=0;
    }
}
signed main(){
    int timee;
    cin>>timee;
    while(timee--){
        cin>>n;
        init();
        int ans=0x7fffffff;
        for(int i=1;i<=n;++i){
            string s;
            cin>>s;
            p[i]=s;
            cin>>a[i]>>b[i];
        }
        int bb=(1ll<<n)-1;
        for(int i=1;i<=bb;i++){
            int tem=1;
            int cnt=0;
            while(tem<=i){
                cnt++;
                if((tem&i)!=0){
                    int x=i^tem;
                    tt[i]=tt[x]+b[cnt];
                    int y=tt[i]-a[cnt];
                    if(y<0) y=0;
                    int temm=y+dp[x];
                    if(dp[i]<temm) {tem<<=1;continue;}
                    else{
                        dp[i]=temm;
                        change(x,i,cnt);
                    }
                }
                tem<<=1;
            }
        }
        cout<<dp[(1<<n)-1]<<endl;
        for(auto i:v[(1<<n)-1]){
            cout<<p[i]<<endl;
        }
    }
    return 0;
}

一遍过!舒服了
写完了确实觉得比较简单