dp学习笔记(背包问题及变型题)

0/1背包问题:

原文请看:
https://blog.csdn.net/qq_37767455/article/details/99086678?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522161301248116780271524703%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=161301248116780271524703&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-1-99086678.pc_search_result_cache&utm_term=01%25E8%2583%258C%25E5%258C%2585%25E9%2597%25AE%25E9%25A2%2598
暴力法枚举求解:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n=5,c=10,w[5]={2,2,6,5,4},v[5]={6,3,5,4,6};
	int k,kk,i;
	int maxvalue=0;
	for(k=0;k<(1<<5);k++){
		kk=k;
		int tempweight=0,tempvalue=0;
		for(i=0;i<5;i++){
			if(kk%2==1){
				tempweight+=w[i];
				tempvalue+=v[i];
			}
			kk/=2;
		}
		if(tempweight<=c &&tempvalue>=maxvalue)
		maxvalue=tempvalue;
	} 
	cout<<maxvalue;
	return 0;
	
	
	
}

个人理解的关键代码:
在这里插入图片描述

比如填写dp[2][3]的时候,如果装物品2(重量是3),那么就相当于把物品1装到了(总容量-3)的背包中,然后总价值加上此价值
不装物品2的时候,相当于只把物品1撞到里面 dp[2][3]=dp[1][3]

其中可以优化的地方:滚动数组

int dp[1001]
itn ans(){
memset(dp,0,sizeof(dp));
for(int i=1;i<=N;i++)
	for(int j=V;j>=bone[i].vol;j--)
		dp[j]=max(dp[j],dp[j-bone[i].vol]+bone[i].val);
return dp[V];

空间复杂度从NV->V 有时候数据太大,不用滚动数组,会MLE
有缺点,获得不到中间路径

例题1:P2392 kkksc03考前临时抱佛脚

在这里插入图片描述

输入输出样例
输入
1 2 1 3
5
4 3
6
2 4 3
输出
20

思路:
在这里插入图片描述
在这里sum是所有题目的总时间 也相当于01背包中的总体积V,01背包中的价值也是这里每一个题目的时间,此题用到了滚动数组优化

#include<bits/stdc++.h>
using namespace std;
int a[5],i,j,k,sum,t,homework[21],dp[2501];
int main(){
	for(i=1;i<=4;i++)
		cin>>a[i];
	for(i=1;i<=4;i++){
		sum=0;	
		for(j=1;j<=a[i];j++)
			{cin>>homework[j];//输入
			sum+=homework[j];}//总时间累加
		for(j=1;j<=a[i];j++)
			for(k=sum/2;k>=homework[j];k--)//只要是总和的一半
				dp[k]=max(dp[k],dp[k-homework[j]]+homework[j]);//01背包
		t+=sum-dp[sum/2];//累加为另一个脑子
		for(j=1;j<=sum/2;j++)
		dp[j]=0;//清零
	}
	cout<<t;//输出
	return 0;
}
#include<bits/stdc++.h>
using namespace std;

int len[4];
int sub[30];
int f[21][1201];

int main(){
    ios::sync_with_stdio(false);
    for(int i=0;i<4;i++) cin>>len[i];
    int tot=0;
    for(int i=0;i<4;i++){
        int v=0;
        for(int j=1;j<=len[i];j++) cin>>sub[j],v+=sub[j];
        sort(sub,sub+len[i]);
        int t1=0;
        for(int j=1;j<=len[i];j++)
            for(int k=0;k<=v/2;k++){
                f[j][k]=f[j-1][k];
                if(k>=sub[j])f[j][k]=max(f[j][k],f[j-1][k-sub[j]]+sub[j]);
                t1=max(f[j][k],t1);
            }
        tot+=max(t1,v-t1);
    }
    cout<<tot<<endl;
    return 0;
}

补充一个学习大佬的搜索办法
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int Left,Right,minn,ans;
int s[5];
int a[21][5];
void search(int x,int y){
	if(x>s[y]){
		minn=min(minn,max(Left,Right));
		return;
	}
	Left+=a[x][y];
	search(x+1,y);
	Left-=a[x][y];
	Right+=a[x][y];
	search(x+1,y);
	Right-=a[x][y];//毫无技巧的搜索回溯
}
int main(){
	cin>>s[1]>>s[2]>>s[3]>>s[4];
	for(int i=1;i<=4;i++){//减少码量
		Left=Right=0;
		minn=19260817;
		for(int j=1;j<=s[i];j++)
			cin>>a[j][i];
		search(1,i);
		ans+=minn;
	}
	cout<<ans;
	return 0;
}

例题2:任务分配

现有n个任务,要交给A和B完成。每个任务给A或给B完成,所需的时间分别为ai和bi。问他们完成所有的任务至少要多少时间。

Input
第一行一个正整数n,表示有n个任务。 接下来有n行,每行两个正整数ai,bi。

Output
一个数,他们完成所有的任务至少要的时间。

Samples
Input Copy
3
5 10
6 11
7 12
Output
12

大佬代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5 + 700;
const ll mod = 1e9+7;

#define mst(x, a) memset( x,a,sizeof(x) )
#define rep(i, a, b) for(int i=(a);i<=(b);++i)
#define dep(i, a, b) for(int i=(a);i>=(b);--i)

ll read() {
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
void out(ll x) {
	int stackk[40];
	if (x < 0) {
		putchar('-');
		x = -x;
	}
	if (!x) {
		putchar('0');
		return;
	}
	int top = 0;
	while (x) stackk[++top] = x % 10, x /= 10;
	while (top) putchar(stackk[top--] + '0');
}
ll qpow(ll a,ll b) {
	ll ans=1;
	while(b) {
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
int n,x[maxn],y[maxn],dp[666][6666];
int main() {
	n=read();
	rep(i,1,n) x[i] = read(),y[i] =read();

	//dp[i][j]  表示前i个任务  a花费j时间   b花费dp[i][j]时间的最小价值
	//dp[i][j] = 给A做  dp[i-1][]

	int s=0;
	for(int i=1 ; i<=n ; i++) {
		s+=x[i];
		for(int j=s ; j>=0 ; j--) {
			dp[i][j] = dp[i-1][j] + y[i];
			if(j>=x[i]) dp[i][j] = min(dp[i][j],dp[i-1][j-x[i]]);
		}
	}
	int ans  = inf;
	rep(i,0,s) ans = min(ans,max(i,dp[n][i]));
	out(ans);
	return 0;
}

/*

*/

例题3:2019

一天,小赵遇到一个只含有2,0,1,9四种字符的字符串,他想知道里面有多少个2019,自己能得多少个不同的2019。只要有一个位置不同就算一个新的2019,如,20199就有2个2019,9012有0个2019。
小赵开心的把今天的事告诉小静,小静听完问:“程序竞赛更好玩吗?”小赵回答:“它不是好不好玩的问题,他是……算了,我把这道题明天讲给你听,你就知道好不好玩了。”小赵为了证明打程序设计竞赛好玩,他找到了你,希望你能做出这道题,看看小赵能得到几个2019,因为结果可能很大,所以结果请对1e9+7进行取模。
在这里插入图片描述

Samples
Input
2019119
220109
Output
4
2

思路:
这算是一个递推的解法,跟大佬学的。dp怎么做不太会,有大佬路过求教。
以220109为例:
循环这些数后,有两个2 所以x1变成2,预估有两个2019,然后循环第三个是0所以x2变成2,循环第四个是1,x3变成2(一直累加又因为x3本身是0),循环第五个是0,那么x2=x2+x1=4 也就是此时有可能的情况为4种(开头两个2 加上后面两个0组成可能4种情况)但是最终答案只看x4的。
循环最后一个是9,所以x4=x4+x3=2 所以前面x2更新为4并没有影响最后的结果 因为0后面没有1进行连续了。

code:

#include<iostream>
#include<string.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;

char s[100005];  
int main(){
 ll i,j,x1,x2,x3,x4,len,ans;
 //string s;
 while(scanf("%s",s)!=EOF){
  x1=x2=x3=x4=0;
  //len=s.length();
  len=strlen(s);
  for(i=0;i<len;i++){
   if(s[i]=='2'){
    x1++;
   }
   else if(s[i]=='0'){
    x2+=x1;
    x2=x2%mod;
   }
   else if(s[i]=='1'){
    x3+=x2;
    x3=x3%mod;
   }
   else if(s[i]=='9'){
    x4+=x3;
    x4=x4%mod;
   } 
  }
  ans=x4%mod;
  printf("%lld\n",ans);
 }
 return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值