动态规划 背包

142 篇文章 1 订阅
142 篇文章 0 订阅

p1466 01背包

dp[i][j]代表前i个数中和为j的方案数,只有(n*(n+1))/2为偶数时才有方案,接下来就是01背包了,这个集合放或不放这个数

题解 P1466 【集合 Subset Sums】 - 「QQ红包」 的博客 - 洛谷博客 (luogu.com.cn)

#include<bits/stdc++.h>
#define ll long long
#define lowbit(a) ((a)&(-a))
//typedef __int128 LL;
using namespace std;
const ll inf=0x3f3f3f3f;
const int mod=1e8-3,N = 3000000 + 5;
const double eps=1e-11;
ll read() {//快读
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
void write(int x) {//快写
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
ll qpow(ll a,ll b){//快速幂
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

ll getinv(ll a,ll mod){return qpow(a,mod-2);}//费马小定理求逆元
bool cmp1(int a,int b){return a>b;}
ll n,dp[45][888],sum;
int main(){
    //freopen("in.txt","r",stdin);
    n=read();
    sum=(n*(n+1))/2;
    if(sum&1){cout<<0<<endl;return 0;}
    dp[1][1]=1;
    dp[1][0]=1;
    for(int i=2;i<=n;i++)
        for(int j=0;j<=sum;j++)
        if(j>i) dp[i][j]=dp[i-1][j]+dp[i-1][j-i];
        else dp[i][j]=dp[i-1][j];
        
        cout<<dp[n][sum/2]<<endl;
    return 0;
}

p1509 两个背包

需要开两个背包,一个存泡了几个女的,一个存花了多少时间

题解 P1509 【找啊找啊找GF】 - Anguei 的博客 - 洛谷博客 (luogu.org)

#include<bits/stdc++.h>
#define ll long long
#define lowbit(a) ((a)&(-a))
//typedef __int128 LL;
using namespace std;
const ll inf=0x3f3f3f3f;
const int mod=1e9+9,N = 3000000 + 5;
const double eps=1e-11;
ll read() {//快读
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
void write(int x) {//快写
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
ll qpow(ll a,ll b){//快速幂
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

ll getinv(ll a,ll mod){return qpow(a,mod-2);}//费马小定理求逆元

inline ll maxm(ll a,ll b,ll c,ll d){
    if(a<b)a=b; if(a<c)a=c; if(a<d)a=d;
    return a;
}
inline ll minm(ll a,ll b,ll c,ll d){
    if(b<a)a=b; if(c<a)a=c; if(d<a)a=d;
    return a;
}
bool cmp1(int a,int b){return a>b;}
ll n,rm[110],rp[110],ti[110],m,r,dp[110][110],f[110][110];
int main(){
   // freopen("in.txt","r",stdin);
    n=read();
    for(int i=1;i<=n;i++) rm[i]=read(),rp[i]=read(),ti[i]=read();
    m=read();r=read();
    for(int i=1;i<=n;i++)
        for(int j=m;j>=rm[i];j--)
            for(int k=r;k>=rp[i];k--)
            {
                if(dp[j][k]<=dp[j-rm[i]][k-rp[i]]){
                    dp[j][k]=dp[j-rm[i]][k-rp[i]]+1;
                    f[j][k]=f[j-rm[i]][k-rp[i]]+ti[i];
                }
                else if(dp[j][k]==dp[j-rm[i]][k-rp[i]]+1){
                    f[j][k]=min(f[j-rm[i]][k-rp[i]]+ti[i],f[j][k]);
                }
            }
    cout<<f[m][r]<<endl;
    return 0;
}

p5322 背包

自己的策略是不会变的,那么可以一开始输入的时候记录一下对于每一个城堡每个人的兵力是多少,然后给每个城堡的每个人的兵力排个序,排好序后,如果第i个城堡的第k个人的兵力是a[i][k]的话,那么派出2*a[i][k]+1的兵力就可以获得i*k个积分了;把兵力当作容量,把a[i][k]*2-1当作体积,把i*k当作价值,那么这又是一个01背包了

题解 P5322 【[BJOI2019]排兵布阵】 - Santiego 的博客 - 洛谷博客 (luogu.com.cn)

#include<bits/stdc++.h>
#include<string>
#define ll long long
using namespace std;
const int maxn =3e7+5;
ll s,n,m,dp[30005],a[220][20005];
int main() {
    //freopen("in.txt","r",stdin);
    scanf("%lld%lld%lld",&s,&n,&m);
    for(int i=1;i<=s;i++)
    for(int j=1;j<=n;j++)
        scanf("%lld",&a[j][i]);
    for(int i=1;i<=n;i++)
        sort(a[i]+1,a[i]+s+1);
    for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
    for(int k=1;k<=s;k++)
    if(j>a[i][k]*2) dp[j]=max(dp[j-a[i][k]*2-1]+k*i,dp[j]);
    printf("%lld\n",dp[m]);
	return 0;
}

19B - Checkout Assistant 01背包

如果第i个物品正在结账,那么可以获得t[i]个物品,再加上第i个物品一共可以获得t[i]+1个物品,我们可以把这个看作体积,需要付的钱看作价值,最后求总价值最小;问题是如何确定容量,可以发现如果一个物品的t[i]足够大,可以让我们拿完这n-1个物品,那么我们只需要算着一个物品的体积就可以了,所以我们可以让容量为max(t[i])+n,这样所有物品的体积必不会超过这个值,而且在最后统计的时候我们也好去寻找最小的ans;

题解 CF19B - cyl06的小窝 - 洛谷博客 (luogu.com.cn)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const ll inf=1e18;
ll n,t[2005],c[2005],dp[5005];
int main(){
    //freopen("in.txt","r",stdin);
    ll v=0;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld",&t[i],&c[i]);
        t[i]++;
        v=max(v,t[i]);
    }
    v+=n;
    for(int i=1;i<=v;i++) dp[i]=inf;dp[0]=0;
    for(int i=1;i<=n;i++)
        for(int j=v;j>=t[i];j--)
        dp[j]=min(dp[j],dp[j-t[i]]+c[i]);
    ll ans=inf;
    for(int i=n;i<=v;i++) ans=min(dp[i],ans);
    printf("%lld\n",ans);
	return 0;
}

P1759 通天之潜水 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

背包打印路径,自己写的挂了,最后还是看的题解,f[i][j][k]表示i,j,k这个状态是选了第i个物品而来的,标记为1,具体看代码; 

题解 P1759 【通天之潜水】 - c_x_c 的博客 - 洛谷博客 (luogu.com.cn)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const ll inf=0x3f3f3f3f;
ll n,m,v,a[110],b[110],c[110],f[110][210][210]={0},dp[210][210];
void print(ll N,ll M,ll V){
    if(N==1){//如果是一 
        if(f[N][M][V])//如果被放过 
        printf("1 ");
        return;
    }
    if(f[N][M][V]){
        print(N-1,M-a[N],V-b[N]);
        printf("%lld ",N);
    }
    if(f[N][M][V]==0) print(N-1,M,V);
    return;
}
int main(){
    //freopen("in.txt","r",stdin);
    scanf("%lld%lld%lld",&m,&v,&n);
    for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int j=m;j>=a[i];j--)
    for(int k=v;k>=b[i];k--){
        if(dp[j][k]<dp[j-a[i]][k-b[i]]+c[i]){
            dp[j][k]=dp[j-a[i]][k-b[i]]+c[i];
            f[i][j][k]=1;
        }
    }
    printf("%lld\n",dp[m][v]);
    print(n,m,v);
	return 0;
}

 但我递归回溯不是很好,下面有一种字符串的方法,很好理解,以后可以用这种方法;

题解 P1759 【通天之潜水】 - dzj 的博客 - 洛谷博客 (luogu.com.cn)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const ll inf=0x3f3f3f3f;
ll n,v,m,a[110],b[110],c[110],dp[210][210];
string ans[210][210];
int main(){
    //freopen("in.txt","r",stdin);
    scanf("%lld%lld%lld",&m,&v,&n);
    for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int j=m;j>=a[i];j--)
        for(int k=v;k>=b[i];k--)
    if(dp[j][k]<dp[j-a[i]][k-b[i]]+c[i]){
        dp[j][k]=dp[j-a[i]][k-b[i]]+c[i];
        ans[j][k]=ans[j-a[i]][k-b[i]]+to_string(i)+" ";
    }
    printf("%lld\n",dp[m][v]);
    cout<<ans[m][v]<<endl;
	return 0;
}

P2340 [USACO03FALL]Cow Exhibition G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一开始是往背包那里想的,但是一开始数据400*1000*1000就觉得不对劲,然后又瞎搞了一种做法只过了36分,,,最后看题解还就是背包,,,不过就算知道了我还是做不出来;

以情商作为容量智商作为价值求完之后再逐个枚举最大值,由于可能是负数,我们就给它加上一个负数最大的绝对值,另外容量可能为负,所以我们不能全部都按照01背包一样倒序枚举,因为如果枚举的s[i]=-6,j=106时,dp[106]会由dp[112]转移了,但dp[112]已经是当前的i不是i-1了,所以可能会出现重复选择的情况,所以如果是容量为负的话,我们就正序枚举(没想到这样是正确的,,,),最后枚举答案的时候别忘了减去400000就行;

题解 P2340 【奶牛会展】 - YJunJ 的博客 - 洛谷博客

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
const ll mod=1e9+7;
ll n,s[404],f[404],dp[800005];
int main(){
	//freopen("in.txt","r",stdin);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld%lld",&s[i],&f[i]);
    for(int i=0;i<=800000;i++) dp[i]=-inf;
    dp[400000]=0;
    for(int i=1;i<=n;i++){
        if(s[i]>=0){
            for(int j=800000;j>=s[i];j--)
                dp[j]=max(dp[j],dp[j-s[i]]+f[i]);
        }
        else{
            for(int j=0;j<=800000+s[i];j++)
                dp[j]=max(dp[j],dp[j-s[i]]+f[i]);
        }
    }
    ll ans=0;
    for(int i=400000;i<=800000;i++){

        if(dp[i]>0) ans=max(ans,dp[i]+i-400000);
    }
    printf("%lld\n",ans);
	return 0;
}

P2946 [USACO09MAR]Cow Frisbee Team S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

01背包,但是容量开不了n*f这个大,考虑用0~f-1也就是f的模数作为容量,所以就会有dp方程

dp[i][j]=(dp[i][j]+dp[i-1][j])%mod+dp[i-1][(j-r[i]+f)%f]%mod 

这样取模是为了避免负数,而且一定是要用二维的,不然容量j取模后可能会小于r[i]这样更新就出错了,还有为了避免一些错误,r[i]从一开始就要取模,并且要注意初始化,虽然是个黄题,但是要注意的细节还是很多的;

#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define lowbit(i) ((-i)&(i))
using namespace std;
const ll inf=1e18;
const ll mod=1e8;
const int maxn=2000006;
ll n,f,r[2005],dp[2005][1005];
int main(){
	//freopen("in.txt","r",stdin);
    scanf("%lld%lld",&n,&f);
    for(int i=1;i<=n;i++) scanf("%lld",&r[i]),r[i]%=f;
    for(int i=1;i<=n;i++) dp[i][r[i]]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<f;j++)
        dp[i][j]=((dp[i][j]+dp[i-1][j])%mod+dp[i-1][(j-r[i]+f)%f]%mod)%mod;
    printf("%lld\n",dp[n][0]);
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

killer_queen4804

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值