【背包问题】

【背包问题】

image

【01背包】

模版题https://www.acwing.com/problem/content/2/
一维:从m~1

模版代码

二维写法

const int N=1010;
int n,m;
int v[N],w[N];
int dp[N][N];
void solve(){
    cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			dp[i][j]=dp[i-1][j];
			if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
		}
	}
    int ans=dp[n][m];
	cout<<ans<<endl;
}

一维写法

const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
void solve(){
    cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++){
		for(int j=m;j>=v[i];j--){//注意滚动数组:从m到1
			dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
		}
	}
    int ans=dp[m];
	cout<<ans<<endl;
}

解题过程

image

题目积累

小苯的Polygon

https://ac.nowcoder.com/acm/contest/104637/E
题目用背包信息点:每根木棍都可以选择用或者不用,如果用的话只能用一次

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const int INF=0x3f3f3f3f;
int t;
int n;
/*
凸多边形:最长边<其他边之和

背包dp
考虑枚举三角形最长边mx
从若干个木棍的集合中,找出一个子集,使得子集和严格>mx,同时子集和最小化

如何知道总和为s的自己是否存在?
f[i]:总和为i的集合是否存在
for(int i=mx+1;i<=1e4;i++) if(f[i]){
      ans=min(ans,mx+i);
      break;
}
*/
void solve(){
      cin>>n;
      vector<int> a(n+1);
      int s=0;
      for(int i=1;i<=n;i++){
            cin>>a[i];
            s=max(s,a[i]);
      }
      sort(a.begin()+1,a.end());
      int ans=INF;
      vector<int> dp(s*n+1,0);
      dp[0]=1;
      //枚举最长边
      for(int i=1;i<=n;i++){
            //子集和严格>最长边
            for(int j=a[i]+1;j<=s*n;j++){
                  if(dp[j]){
                        ans=min(ans,a[i]+j);//注意这里要加上最长边!
                        break;
                  }
            }
            //从最大值开始枚举:01背包 从上一个状态转移(只能从前i个棍子里选
            for(int j=s*n;j>=a[i];j--){
                  dp[j] |= dp[j-a[i]];
            }
      }
      if(ans==INF) cout<<"-1"<<endl;
      else cout<<ans<<endl;
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>t;
      while(t--) solve();
      return 0;
}

完全背包

模版题https://www.acwing.com/problem/content/3/

模版代码

const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
void solve(){
    cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++){
		for(int j=v[i];j<=m;j++){
			dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
		}
	}
    cout<<dp[m]<<endl;
}

解题过程

image
image

题目积累

建筑入门

https://ac.nowcoder.com/acm/contest/101921/E

思路

image

代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const int N=1e6+10,M=55;
/*
【思路】
满足题目的最小k值->n的排列 求1x1+2x2+...+nxn=n*(n+1)*(2*n+1)/6 -> 比这个还小那就不满足(-1)
差值:k-n*(n+1)*(2*n+1)/6
以此为基础增加各项:从下到上进行增加
 - 考虑+1的情况:选择[i,n]+1:n阶排列后缀和 -> (n+i)*(n-i+1)/2
 - 考虑选择n个x(加在n行 n-1行 n-2行...加多少) 使得x*(n+i)*(n-i+1)/2全部相加起来=差值
 ->完全背包求解:
 设f[i][j]为在[i,n]下进行加法操作能够达到和为j的一种x/利用[i,n]的贡献使得总和为j(只需要记录可不可行)
 ->dp结束时 如果f[1][k-最小k值]如果为-1 -> 无解
*/
int n,k;
int dp[M][N],chs[M],ans[M];
int cnt[M];//记录方案数
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>n>>k;
      //计算差值
      int g=k-(n*(n+1)*(2*n+1)/6);
      if(g<0){
            cout<<"-1"<<endl;
            return 0;
      }
      for(int i=1;i<=n;i++) chs[i]=(n+i)*(n-i+1)/2;
      //初始化
      memset(dp,-1,sizeof dp);
      dp[n][0]=0;
      for(int i=1;i<=g;i++) dp[n+1][i]=-1;//不操作任何数肯定达不到->-1
      //从下往上dp
      for(int i=n;i>=1;i--){
            for(int j=0;j<=g;j++){
                  //如果底层有方案能够达到,那么上层自然有一个方案是+0
                  if(dp[i+1][j]!=-1) dp[i][j]=0;
            }
            //完全背包dp
            for(int j=chs[i];j<=g;j++){
                  if(dp[i][j-chs[i]]!=-1) dp[i][j]=dp[i][j-chs[i]]+1;
            }
      }
      if(dp[1][g]==-1){
            cout<<"-1"<<endl;
            return 0;
      }
      //回溯求值
      int now=g;
      int alt=0;//从第一层开始:要累加
      for(int i=1;i<=n;i++){
            while(now>=chs[i] && dp[i][now]>0){
                  cnt[i]++;
                  now-=chs[i];
            }
            alt+=cnt[i];
            ans[i]=i+alt;
      }
      for(int i=1;i<=n;i++){
            cout<<ans[i]<<" ";
      }
      return 0;
}

多重背包

解题过程

image

分组背包

【杂题积累】

Subarray Sum Divisibility

https://atcoder.jp/contests/abc419/tasks/abc419_e

以取模的条件进行转移->类似滚动的方式 要运用ndp辅助转移!

题目大意

74638554-806a-433b-b339-5273c5e8126b

解题思路

42d8035614fb9c15288cd89d33ec71bb_720

代码

const int N=510;
int n,m,l;
int a[N];
int w[N][N];
void solve(){
    cin>>n>>m>>l;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=l;i++){
        for(int j=i;j<=n;j+=l){
            for(int k=0;k<m;k++){
                w[i][(a[j]+k)%m]+=k;
            }
        }
    }
    vector<int> dp(m,inf_int);
    dp[0]=0;
    for(int i=1;i<=l;i++){
    	vector<int> ndp(m,inf_int);
        for(int j=0;j<m;j++){
            for(int k=0;k<m;k++){
                ndp[(j+k)%m]=min(ndp[(j+k)%m],dp[j]+w[i][k]);
            }
        }
        dp=ndp;
    }
    cout<<dp[0]<<endl;
}
posted @ 2025-02-20 01:17  White_ink  阅读(9)  评论(0)    收藏  举报