ACWing 背包模型 day37

今天结束了背包类的题

明天就开始状态机和状态压缩类的拉~

今天和同学聊起来好像进不了省选

非常遗憾的同时 也感觉自己技不如人 甘拜下风啊!

好像今天的训练时长又要短了 感觉自己就是又菜又想拿奖 

思想问题很大啊!

AcWing 1013. 机器分配

我超 今天怎么会是 

简单题 没看出分组背包 也只会暴力输出路径 没加if多调十多分钟呃呃

感觉已经废了

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
const int M = 1e4+10;
const int mod = 998244353;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N],n,m,path[N],cnt;
pair<int,int>p[N][N];
void dfs(int i,int j){
    if(!i&&!j)return;
    for(int k=0;k<=m;k++){
        if(f[i-1][j-p[i][k].first]+p[i][k].second==f[i][j]){
            path[++cnt]=k;
            dfs(i-1,j-k);
            return;
        }
    }
}
signed main(){
    fast
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            p[i][j].first=j;
            cin>>p[i][j].second;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j]=f[i-1][j];
            for(int k=1;k<=m;k++){
                if(j>=p[i][k].first)f[i][j]=max(f[i][j],f[i-1][j-p[i][k].first]+p[i][k].second);
            }
        }
    }
    cout<<f[n][m]<<endl;
    dfs(n,m);
    for(int i=1;i<=cnt;i++){
        cout<<i<<' '<<path[n-i+1]<<endl;
    }
    return ~~(0^_^0);
}

426. 开心的金明

#include <bits/stdc++.h>
using namespace std;
const int N = 3e4+10;
const int M = 1e4+10;
const int mod = 998244353;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N];
signed main(){
    fast
    int V,n;cin>>V>>n;
    for(int k=1;k<=n;k++) {
        int v,x;cin>>v>>x;
        for(int j=V;j>=v;j--){
            f[j]=max(f[j],f[j-v]+v*x);
        }
    }
    cout<<f[V]<<endl;
    return ~~(0^_^0);
}

10. 有依赖的背包问题

难 但是没有多重3难 毕竟我从来没学过树形dp

不过也不是不能想的那种 注释代码里写了

提一嘴思路

为什么是树形? 这个图给的就是树形 你说呢

以前我们都是咋枚举决策的 基本上是都把k层 暴力for一遍

但是这个就不一样了 因为你状态很多 基本上是2^n

所以我们会把状态表示关于V

这样子的O(nVV)

#include <bits/stdc++.h>
using namespace std;
const int N = 100+10;
const int M = 1e4+10;
const int mod = 998244353;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,m,h[N],e[N<<1],ne[N<<1],idx,w[N<<1],v[N<<1],root,f[N][N];
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u){
    for(int i=h[u];~i;i=ne[i]){
        int s=e[i];
        dfs(s);//先递归到最底层,自下往上开始dp
        for(int j=m-v[u];j>=0;j--){//我们不妨设我们背包大家被减去了vu,不然不好写,那自然后面要更新
            for(int k=0;k<=j;k++){
                f[u][j]=max(f[u][j],f[u][j-k]+f[s][k]);//这里我才搞清楚,我们dp是让几个子节点,一定混合都有枚举出最优解,
            }                                              //最开始想的是 这个不是只和一个子节点 枚举了一遍吗
        }                                                  //其实他是每个子节点都枚举了一遍 我们只是每次跳出去更新了一次
    }
    for(int i=m;i>=v[u];i--)f[u][i]=f[u][i-v[u]]+w[u];//这里开始就是更新,要是v大于vu的自然要加上wu
    for(int i=0;i<v[u];i++)f[u][i]=0;//小于的就根本进不去,也不准用其值,我们必须置为0
}
signed main(){
    fast
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        int c;cin>>v[i]>>w[i]>>c;
        if(c==-1)root=i;//和编号连边,根节点我们也只要编号
        else add(c,i);
    }
    dfs(root);//从根节点递归
    cout<<f[root][m]<<Endl;//f表示为i节点下并且选择i节点的j体积下的max
    return ~~(0^_^0);
}

11. 背包问题求方案数

和我想的一摸一样呢 再维护一个cnt数组就可以了!

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N],g[N],n,m;
signed main(){
    fast
    cin>>n>>m;
    g[0]=1;
    int maxv=0;
    for(int i=1;i<=n;i++){
        int v,w;cin>>v>>w;
        for(int j=m;j>=v;j--){
            int mx=max(f[j],f[j-v]+w);
            int cnt=0;
            if(mx==f[j])cnt+=g[j];
            if(mx==f[j-v]+w)cnt+=g[j-v];
            g[j]=cnt%mod;
            maxv=max(maxv,mx);
            f[j]=mx;
        }
    }
    int cnt=0;
    for(int i=0;i<=m;i++)if(maxv==f[i])cnt=(cnt+g[i])%mod;
    cout<<cnt<<endl;
    return ~~(0^_^0);
}

12. 背包问题求具体方案

这道题可以用string 数组来记录 路径 正着做

也可以反着做

而反着做的话我们的f_i_j i层就是从n到1

这样的状态表示的意思大概就是 i~n个物品 容量为j 时的max

我们的max自然是f_1_m

然后就可以从1_m开始转换

要是1_m可以被选择那么就是f[i][j]==f[i+1][j-v[i]]+w[i]

反之就是不能被选f_i_j==f_i+1_j

每次被选过后我们f_i_j就会转换为下一层 j也会变换

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,m,f[N][N],v[N],w[N];
signed main(){
    fast
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
    for(int i=n;i>=1;i--){
        for(int j=1;j<=m;j++){
            f[i][j]=f[i+1][j];
            if(j>=v[i])f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
        }
    }
    int j=m;
    for(int i=1;i<=n;i++){
        if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i]){
            cout<<i<<' ';
            j-=v[i];
        }
    }
    return ~~(0^_^0);
}

487. 金明的预算方案

有依赖的背包问题 又是数组开小了卡很久

只注意到了都是10的整数倍

然后t了一发 加O2 能过 大概是O(nVV/100) 就是8的级别

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks") 
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4+2010;
const int M = 1e4+10;
const int mod = 998244353;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,m,h[N],e[N],ne[N],idx,w[N],v[N],f[65][N];
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u){
    for(int i=h[u];~i;i=ne[i]){
        int s=e[i];
        dfs(s);
        for(int j=m-v[u];j>=0;j-=10){
            for(int k=0;k<=j;k+=10){
                f[u][j]=max(f[u][j],f[u][j-k]+f[s][k]);
            }
        }
    }
    for(int i=m;i>=v[u];i-=10)f[u][i]=f[u][i-v[u]]+w[u];
    for(int i=0;i<v[u];i+=10)f[u][i]=0;
}
signed main(){
    fast
    cin>>m>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        int c;cin>>v[i]>>w[i]>>c;
        w[i]*=v[i];
        add(c,i);
    }
    dfs(0);
    cout<<f[0][m]<<Endl;
    return ~~(0^_^0);
}

但是我们好像可以发现这里还有一句话

每个主件可以有0个、1个或2个附件。

那我们可以变成一个分组背包问题 每次反正要跟上主件

734. 能量石

一遍就写过了 感觉还不错

首先拿到这道题

我们都会直接想 直接01背包dp就是了

可是为啥不行呢

因为我们dp时 要求的 顺序是无序的

也就是如果直接把这个拿进去dp

f[j]=max(f[j],f[j-s]+e-(j-s)*l);

这个方程会因为顺序的固定让s只有一种情况

肯定是错误的

而我们贪心的作用就是 找出肯定正确的那个s

然后再来dp

为啥还要dp?

其实我们贪心出来的 只是一个最优解次序

吃不吃 这个其实我们是不得而知的 

证一下这个贪心为啥正确

我们假设有俩数 i,i+1

要是先算i再算i+1        要是先算i+1再算i

Ei+Ei+1-Si*Li+1          Ei+1 + Ei + Si+1 * Li

删去相同项 我们 可以发现 就是 Si*Li+1 和 Si+1 * Li 相比谁在前 我们的总价值会不减少

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
const int M = 1e4+10;
const int mod = 998244353;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,t,m,f[N];
struct Stone{
    int  s,e,l;
    bool operator < (const Stone&W)const{
        return  W.l * s < W.s * l;
    }
}stone[N];
signed main(){
    fast
    cin>>t;
    for(int C=1;C<=t;C++){
        m=0;
        cin>>n;
        memset(f,0,sizeof f);
        for(int i=1;i<=n;i++){
            int s,e,l;cin>>s>>e>>l;
            m+=s;
            stone[i]={s,e,l};
        }
        sort(stone+1,stone+n+1);
        int res=0;
        for(int i=1;i<=n;i++){
            int s=stone[i].s,e=stone[i].e,l=stone[i].l;
            for(int j=m;j>=s;j--){
                f[j]=max(f[j],f[j-s]+e-(j-s)*l);
                res=max(res,f[j]);
            }
        }
        printf("Case #%d: %d\n",C,res);
    }
    return ~~(0^_^0);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值