学习记录 动态规划实时更新
这里是目录
- 写在读前:
- Part1 前景知识:
- Part2 具体模型:
- 简单模型:
- 简单递推与数学:[P1077 [NOIP2012 普及组] 摆花](https://www.luogu.com.cn/problem/P1077):
- 思维+递推+一点点数学和hash的拼多多题?[CF1536C-Diluc and Kaeya](https://codeforces.com/contest/1536/problem/C)
- 思维+递推 [CF1535C-Unstable String](https://codeforces.com/contest/1535/problem/C):
- 四维dp模板:[P1006传纸条](https://www.luogu.com.cn/problem/P1006),[P1004方格取数](https://www.luogu.com.cn/problem/P1004):
- 区间模型:
- 合并型模板:[51Nod1021石子归并](https://vjudge.z180.cn/problem/51Nod-1021)
- 环形区间模板:[P1063 能量项链](https://www.luogu.com.cn/problem/P1063)
- 逆向思考+合并型:[分治——19河北省赛C](https://ac.nowcoder.com/acm/contest/903/C)
- 理解递推 [P3205 合唱队](https://www.luogu.com.cn/problem/P3205)
- 高精度+扩展型区间:[P1005 [NOIP2007 提高组] 矩阵取数游戏](https://www.luogu.com.cn/problem/P1005)
- 区间dp求权值+线性dp用权值求解:[HDU2476 String painter](http://acm.hdu.edu.cn/showproblem.php?pid=2476)
- 区间dp求权值+区间dp用权值求解:[P1026 [NOIP2001 提高组] 统计单词个数](https://www.luogu.com.cn/problem/P1026)
- 背包模型:
- 01背包:
- 典型例题与详解 (01背包) :
- 模板:[洛谷—P1048 \ NOIP2005 普及组\ 采药](https://www.luogu.com.cn/problem/P1048)
- 可行性问题:[洛谷—P1049 \NOIP2001 普及组\ 装箱问题](https://www.luogu.com.cn/problem/P1049)
- 贪心+01背包 [牛客小白月赛35J——溪染的优惠券](https://ac.nowcoder.com/acm/contest/11212/J)
- 完全背包:
- 模板:[洛谷—P1616 疯狂的采药](https://www.luogu.com.cn/problem/P1616)
- 多重背包
- 多解经典题:[POJ1742 Coins](http://poj.org/problem?id=1742)
- [P1782 旅行商的背包](https://www.luogu.com.cn/problem/P1782)
写在读前:
此博客旨在系统的总结笔者学习动态规划问题的过程,仅当学习笔记供读者使用。
Part1 前景知识:
- 面向问题:多阶段决策问题。
- 前提条件:
<1. 最优化原理:可以划分为若干有限个子结构,每个子结构的最优状态构成最终的问题最优解;
<2. 无后效性原则:在当前状态进行动作不会影响已经形成的最优子结构。 - 基本概念:
<1. 阶段:问题的子结构;
阶段变量:阶段的具象化表示。
<2. 状态:一个阶段具有若干个状态,多个状态构成一个子阶段;
状态变量:状态的具象化表示。
< 3.决策:将当前状态转移到下一个状态的行动;
决策变量:用来描述决策;
决策允许集合:当前状态可进行的所有决策的集合。
<4. 策略:每个状态进行的决策纵向连接构成一种策略;
最优策略:使状态最优化的策略。 - 过程描述:
<1. 正推:即递推;
<2. 倒推:即记忆化搜索。 - 解题思路:
<1. 抽丝剥茧设计状态,<2. 寻找状态转移方程。
Part2 具体模型:
简单模型:
线性是指状态是线性分布的,中规中矩的将状态从0递推至n。
简单递推与数学:P1077 [NOIP2012 普及组] 摆花:
题目大意:共n种花,花的数量用一个长度为n的数组表示,a[i]代表第i种花有a[i]盆,需要使用这些花摆成一个长度为m的序列,同类花必须连续且摆放顺序按照花的下标大小排序,求不同摆放种类的数量。
状态设计:显然 dp[i][j] 代表使用前 i 种花摆 j 盆的不同序列数量。
但是由于a[i] 的限制,此方程无法记录每种花使用了多少盆,故会出现类似于“将多重背包做成完全背包”的错误。
那么就要考虑一种可以记录每种花使用数量的方程,我们使用第二维花的数量来进行转移,每次考虑给上一个状态,即已经使用了 i-1 种花的状态补花。即:
#include<bits/stdc++.h>
#define MOD 1000007
#define int long long
using namespace std;int n,m,sum=0;
int dp[105][105];
int a[105];signed main()
{scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++) scanf("%lld",&a[i]);for(int i=0;i<=n;i++) dp[i][0]=1;for(int i=1;i<=n;i++){sum+=a[i];int binary=min(sum,m);for(int j=1;j<=binary;j++){int temp=0;for(int k=j;k>=max((long long)0,j-a[i]);k--){temp=(temp%MOD+dp[i-1][k]%MOD)%MOD;}dp[i][j]=temp%MOD;}}printf("%lld\n",dp[n][m]);return 0;
}
思维+递推+一点点数学和hash的拼多多题?CF1536C-Diluc and Kaeya
题目大意:给一个长度为n的只包含D和K的字符串,要求你对字符串的每个前缀找到一种分块的最大数量,要求每块内D和K的比值都相等。
数据范围:字符串长度不超过5e5。
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;int t,n,cnt1,cnt2;
int dp[500005];int main()
{scanf("%d",&t);while(t--){scanf("%d",&n);getchar();int cnt1=0,cnt2=0;map<PII,int> dp;for(int i=1;i<=n;i++){char temp=getchar();if(temp=='D') cnt1++;if(temp=='K') cnt2++;int GCD=__gcd(cnt1,cnt2);PII now={cnt1/GCD,cnt2/GCD};dp[now]++;printf("%d ",dp[now]);}printf("\n");}return 0;
}
思维+递推 CF1535C-Unstable String:
题目大意:给一个长度为n的只包含"0、1、?"的字符串,“?”可以被替换为“0”或“1”,求01交替出现的连续子串的最大数量。
状态转移:判断能不能续上上一个同类串即可,续的上的话该位置形成的贡献数量等于上一个+1,否则的话相当于改变交替01串类型,可直接该类型为0,另一类型为1。
#include<bits/stdc++.h>
#define int long long
using namespace std;int t,dp[3][200005];
char s[200005];signed main()
{scanf("%lld",&t);while(t--){cin>>s+1;int n=strlen(s+1),res=0;//dp[0][0]=dp[0][1]=0;memset(dp,0,sizeof(dp));//memset会影响时间, 但是在cf里面没有tle, 懒得改了QAQfor(int i=1;i<=n;i++){if(s[i]=='?'){dp[1][i]=dp[1][i-1]+1;dp[0][i]=dp[0][i-1]+1;}else if(s[i]-'0'==i%2) dp[1][i]=dp[1][i-1]+1;else dp[0][i]=dp[0][i-1]+1;res+=max(dp[0][i],dp[1][i]);}printf("%lld\n",res);}return 0;
}
四维dp模板:P1006传纸条,P1004方格取数:
题目大意:类似于n*m数塔问题,要计算两条路线的最优值。
状态设计:dp[i][j][k][l] 代表第一个人走到 (i, j) 时,第二个人走到 (k, l) 时的最优状态。
状态转移:
//当前位置可以从上和左两个状态转移过来,又因为两个人,所以可以从上一层共4个状态中取最优值dp[i][j][k][l]=a[i][j]+a[k][l]+max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])));//如果第一个人和第二个人走到相同点,需要去一下重if(i==k&&j==l) dp[i][j][k][l]-=a[i][j];
#include<bits/stdc++.h>
using namespace std;int n,m;
int dp[55][55][55][55],a[105][105];int main()
{scanf("%d%d",&n,&m);memset(a,0,sizeof(a));for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&a[i][j]);}}//int t1,t2,t3;//while(scanf("%d%d%d",&t1,&t2,&t3),t1!=0) a[t1][t2]=t3;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int k=1;k<=n;k++){for(int l=1;l<=m;l++){//if(i==k&&j==l) continue;dp[i][j][k][l]=a[i][j]+a[k][l]+max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])));if(i==k&&j==l) dp[i][j][k][l]-=a[i][j];}}}}printf("%d",dp[n][m][n][m]);return 0;
}
区间模型:
合并型模板:51Nod1021石子归并
dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+pre[r]-pre[l-1]);
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;int n;
long long a[105],dp[105][105],pre[105];int main()
{scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%lld",&a[i]),dp[i][i]=0,pre[i]=pre[i-1]+a[i];for(int len=1;len<n;len++){ //一维枚举区间长度for(int l=1;l+len<=n;l++){ //二维枚举区间端点int r=l+len;dp[l][r]=INF;for(int k=l;k<r;k++){ //三维更新操作dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+pre[r]-pre[l-1]);}}}printf("%lld\n",dp[1][n]);return 0;
}
环形区间模板:P1063 能量项链
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;int n,res;
PII a[205];
int dp[205][205];int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i].first); if(i!=1) a[i-1].second=a[i].first;else a[n].second=a[i].first;}for(int i=n+1;i<=2*n;i++) a[i]=a[i-n];//一维枚举区间长度 for(int len=1;len<n;len++){//二维枚举左端点 for(int l=1;l+len<=2*n;l++){int r=l+len;for(int k=l;k<r;k++){dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]+a[l].first*a[k].second*a[r].second);res=max(res,dp[l][r]);}}}printf("%d\n",res);return 0;
}
逆向思考+合并型:分治——19河北省赛C
这是第一道在赛中解决的区间dp(当时耗时1hour 30min左右),赛后重看发现其实也是一道很裸的区间dp,当时也是经验不够,总结一下:区间dp一定要学会逆向思维。
思路讲解:因为一定要选取序列中的每个数,从已选0个到全部选取,但是正着选值代价并不好计算,那么逆着考虑,每次选择区间,区间外就是已经被选完的,直到选择[1,n]到刚开始0个选取的状态。
状态设计:dp[l][r]代表[l,r]以l-1,r+1为边界,选取[l,r]内全部值需要的代价。
状态转移:枚举每个选取数,[l,k-1]+[k+1,r]
//枚举每个选取数dp[l][r]=min(dp[l][r],cost[k]*(r-l)+dp[l][k-1]+dp[k+1][r]);
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;int t,n;
long long cost[105],res=0;
long long dp[105][105];int main()
{scanf("%d",&t);while(t--){memset(dp,0,sizeof(dp));scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%lld",&cost[i]),dp[i][i]=0;for(int len=1;len<=n;len++){for(int l=1;l+len<=n;l++){int r=l+len;dp[l][r]=min((cost[l]*(r-l)+dp[l+1][r]),(cost[r]*(r-l)+dp[l][r-1]));for(int k=l+1;k<r;k++){dp[l][r]=min(dp[l][r],cost[k]*(r-l)+dp[l][k-1]+dp[k+1][r]);}}}printf("%lld\n",dp[1][n]);}return 0;
}
理解递推 P3205 合唱队
int dp[1005][1005][2];
//dp0代表从左插入,dp1代表从右插入
状态转移:
a[i]从左端插入到l的位置,则a[i-1]可能在l+1或者r的位置;
a[i]从右端插入到r的位置,则a[i-1]可能在l或者r-1的位置 。
if(a[l]<a[l+1]) dp[l][r][0]+=dp[l+1][r][0];if(a[l]<a[ r ]) dp[l][r][0]+=dp[l+1][r][1];if(a[r]>a[ l ]) dp[l][r][1]+=dp[l][r-1][0];if(a[r]>a[r-1]) dp[l][r][1]+=dp[l][r-1][1];dp[l][r][0]%=MOD;dp[l][r][1]%=MOD;
#include<bits/stdc++.h>
#define MOD 19650827
using namespace std;int n,a[1005];
int dp[1005][1005][2];
//dp0代表从左插入,dp1代表从右插入int main()
{scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]),dp[i][i][0]=1;for(int len=1;len<n;len++){for(int l=1;l+len<=n;l++){int r=l+len;if(a[l]<a[l+1]) dp[l][r][0]+=dp[l+1][r][0];if(a[l]<a[ r ]) dp[l][r][0]+=dp[l+1][r][1];if(a[r]>a[ l ]) dp[l][r][1]+=dp[l][r-1][0];if(a[r]>a[r-1]) dp[l][r][1]+=dp[l][r-1][1];dp[l][r][0]%=MOD;dp[l][r][1]%=MOD;}}printf("%d\n",(dp[1][n][0]+dp[1][n][1])%MOD);return 0;
}
高精度+扩展型区间:P1005 [NOIP2007 提高组] 矩阵取数游戏
//当前区间可以由 靠左的小区间向右扩展 或者靠右的小区间向左拓展 得来,两种值取最大值。dp[l][r]=max((dp[l+1][r]+a[i][l]),dp[l][r-1]+a[i][r])*2;
/*错误写法*/ if(dp[l+1][r]>=dp[l][r-1]){/*错误写法*/ dp[l][r]=dp[l+1][r]*2+a[i][l]*2;
/*错误写法*/ }else{/*错误写法*/ dp[l][r]=dp[l][r-1]*2+a[i][r]*2;
/*错误写法*/ }
#include<bits/stdc++.h>
#define int long long
using namespace std;__int128 n,m;
__int128 dp[105][105];
__int128 a[105][105];__int128 read(){__int128 x=0,f=1;char ch=getchar();while(!isdigit(ch)&&ch!='-')ch=getchar();if(ch=='-')f=-1,ch=getchar();while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return f*x;
}
void print(__int128 x){if(x<0)putchar('-'),x=-x;if(x>9)print(x/10);//注意这里是x>9不是x>10 (2019.10 wa哭了回来标记一下)putchar(x%10+'0');
}signed main()
{n=read(),m=read();for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)a[i][j]=read(); }__int128 res=0;for(int i=1;i<=n;i++){memset(dp,0,sizeof(dp));for(int j=1;j<=m;j++) dp[j][j]=a[i][j]*2;for(int len=1;len<=m;len++){for(int l=1;l+len<=m;l++){int r=l+len;dp[l][r]=max((dp[l+1][r]+a[i][l]),dp[l][r-1]+a[i][r])*2;}}res+=dp[1][m];}print(res);return 0;
}
区间dp求权值+线性dp用权值求解:HDU2476 String painter
题目大意:
多实例给两个字符串A,B,可以将A中任意区间内的字符替换为一种字母,求将A串变为B串的最小替换次数。
思路讲解:
//为何不能直接A串转B串?——菜鸡也不知道啊,直接看题解了QAQ
for(int len=1;len<n;len++){for(int l=1;l+len<=n;l++){int r=l+len;if(b[l]==b[r]) g[l][r]=g[l][r-1];for(int k=l;k<r;k++){g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]);}}
}
for(int i=1;i<=n;i++){if(a[i]==b[i]) f[i]=f[i-1];else{f[i]=INF;for(int k=0;k<i;k++){f[i]=min(f[i],f[k]+g[k+1][i]);}}
}
//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;int f[105],g[105][105];
char a[105],b[105];int main()
{while(cin>>a+1>>b+1){int n=strlen(a+1);//memset(f,INF,sizeof(f));memset(g,INF,sizeof(g));for(int i=1;i<=n;i++) g[i][i]=1;for(int len=1;len<n;len++){for(int l=1;l+len<=n;l++){int r=l+len;if(b[l]==b[r]) g[l][r]=g[l][r-1];for(int k=l;k<r;k++){g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]);}}}for(int i=1;i<=n;i++){if(a[i]==b[i]) f[i]=f[i-1];else{f[i]=INF;for(int k=0;k<i;k++){f[i]=min(f[i],f[k]+g[k+1][i]);}}}printf("%d\n",f[n]);}return 0;
}
区间dp求权值+区间dp用权值求解:P1026 [NOIP2001 提高组] 统计单词个数
题目大意:
给一个字符串和一个单词表,要求让你将字符串分成k份,统计每份中单词个数总和,单词字母可以重叠,但是一个单词的首字母不能重复使用。
求出每部分得分后,再用区间dp枚举划分区域求得最优值即可。
完整代码:
#include<bits/stdc++.h>
using namespace std;int n,m,k,Len;
string text,pattern[105],ts;int sum[205][205],dp[505][505];bool judge(int l,int r){string temp=text.substr(l,r-l+1);for(int i=1;i<=m;i++){if(temp.find(pattern[i])==0) return true;}return false;
}int main()
{scanf("%d%d",&n,&k);text=" ";for(int i=1;i<=n;i++) cin>>ts,text+=ts;Len=text.size()-1;scanf("%d",&m);for(int i=1;i<=m;i++){cin>>pattern[i];//pattern[i]=" "+pattern[i];}for(int r=Len;r>=1;r--){for(int l=r;l>=1;l--){sum[l][r]=sum[l+1][r];if(judge(l,r)) sum[l][r]++;}}dp[0][0]=0;for(int i=1;i<=Len;i++) dp[i][i]=dp[i-1][i-1]+sum[i][i];for(int i=1;i<=Len;i++) dp[i][1]=sum[1][i];for(int i=1;i<=Len;i++){for(int j=1;j<=k&&j<i;j++){for(int mid=j;mid<i;mid++){dp[i][j]=max(dp[i][j],dp[mid][j-1]+sum[mid+1][i]);}}}printf("%d\n",dp[Len][k]);return 0;
}
背包模型:
在一定限制条件内根据对象属性取得最优解;
<1. 设计状态:dp[i][j]代表容量为j的背包取前i个物品的最优解;
<2. 转移方程:dp[j] = max(dp[j], dp[j - v[i]] + w[i] ) 。
01背包:
物品不能重复取,即每个对象最多只能纳入背包1次。
典型例题与详解 (01背包) :
模板:洛谷—P1048 \ NOIP2005 普及组\ 采药
#include<bits/stdc++.h>
using namespace std;int m,n; //m为背包容量,n为物品个数
int w[105],v[105]; //物品的两个属性weight和value
int dp[1005]; //滚动数组int main()
{scanf("%d%d",&m,&n);for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);for(int i=1;i<=n;i++){for(int j=m;j>0;j--) dp[j]=(j>=v[i]?max(dp[j],dp[j-v[i]]+w[i]):dp[j]); //利用三目运算判断下标是否越界}printf("%d\n",dp[m]);return 0;
}
可行性问题:洛谷—P1049 \NOIP2001 普及组\ 装箱问题
#include<bits/stdc++.h>
using namespace std;int dp[20005];
int n,v,a[35];int main()
{scanf("%d%d",&v,&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);sort(a+1,a+1+n);dp[0]=1;for(int i=1;i<=n;i++){for(int j=v;j>0;j--){if(j-a[i]>=0&&dp[j-a[i]]) dp[j]=1;}}for(int i=v;i>0;i--){if(dp[i]){printf("%d\n",v-i);break;}}return 0;
}
贪心+01背包 牛客小白月赛35J——溪染的优惠券
题目大意:
一件商品k元(背包容量),有n张优惠券,每张优惠券需要满a元才能减b元,可叠加使用,求商品最低可以减到多少钱。
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;bool cmp(PII p,PII q){return p.first-p.second>q.first-q.second;
}int n,k;
PII a[10005];
int dp[10005];int main()
{scanf("%d%d",&n,&k);for(int i=1;i<=n;i++){scanf("%d%d",&a[i].first,&a[i].second);}sort(a+1,a+1+n,cmp);dp[k]=1;for(int i=1;i<=n;i++){for(int j=a[i].first;j<=k;j++) dp[j-a[i].second] |= dp[j];}int res=0;for(int i=0;i<=k;i++){if(dp[i]==1){printf("%d\n",i);break;}}return 0;
}
完全背包:
模板:洛谷—P1616 疯狂的采药
#include<bits/stdc++.h>
using namespace std;int m,n;
long long w[10005],v[10005];
long long dp[10000005];int main()
{scanf("%d%d",&m,&n);for(int i=1;i<=n;i++) scanf("%lld%lld",&v[i],&w[i]);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++) dp[j]=(j>=v[i]?max(dp[j],dp[j-v[i]]+w[i]):dp[j]);}printf("%lld\n",dp[m]);return 0;
}
多重背包
多解经典题:POJ1742 Coins
解法一:
多重背包可行性问题:一维枚举使用前 i 种硬币能构成的总值数,二维判断每种值能否可行,并维护当前使用的硬币数量,只有小于Ci的时候才能使用这种硬币。
//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;int n,m;
bool dp[100005];
int a[105],c[105],sum[100005];int main()
{while(scanf("%d%d",&n,&m),n!=0){memset(dp,0,sizeof(dp));for(int i=1;i<=n;i++) scanf("%d",&a[i]);for(int i=1;i<=n;i++) scanf("%d",&c[i]);//迭代多重背包dp[0]=1;for(int i=1;i<=n;i++){memset(sum,0,sizeof(sum));for(int j=a[i];j<=m;j++){if(!dp[j]&&dp[j-a[i]]&&sum[j-a[i]]<c[i]){dp[j]=1;sum[j]=sum[j-a[i]]+1;}}}int res=0;for(int i=1;i<=m;i++) if(dp[i]) res++;printf("%d\n",res);}return 0;
}
解法二:
多重背包+01背包+二进制拆分:如果c[i] 够用,即只使用a[i]凑够m还有剩余,可以把a[i] 当作完全背包,否则进行二进制拆分转化为01背包。
//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;int n,m,a[105],c[105];
bool dp[100005];void complePack(int weight){for(int j=weight;j<=m;j++){if(dp[j-weight]) dp[j]=true;}
}void onezeroPack(int weight){for(int j=m;j>=weight;j--){if(dp[j-weight]) dp[j]=true;}
}int main()
{while(scanf("%d%d",&n,&m),n!=0){for(int i=1;i<=m;i++) dp[i]=false;for(int i=1;i<=n;i++) scanf("%d",&a[i]);for(int i=1;i<=n;i++) scanf("%d",&c[i]);dp[0]=1; for(int i=1;i<=n;i++){if(a[i]*c[i]>=m){complePack(a[i]);}else{/*int BS=1;while(c[i]>BS){onezeroPack(BS*a[i]);c[i]-=BS;BS*=2;}onezeroPack(c[i]*a[i]);*///二进制拆分 for(int j=1;j<=c[i];j<<=1){if(a[i]*j<=m) onezeroPack(a[i]*j);c[i]-=j;}//剩余无法继续拆的 if(c[i]>0&&a[i]*c[i]<=m) onezeroPack(a[i]*c[i]);}}int res=0;for(int i=1;i<=m;i++) if(dp[i]) res++;printf("%d\n",res);}return 0;
}
P1782 旅行商的背包
思路分析:
第一种物品就是裸的多重背包;
第二种物品要枚举它在背包内的体积:
由于不同的体积拥有不同的权重,所以对于dp数组的每个状态,采用类似于01背包倒序枚举的思想,就不会出现重复相加的情况。
题后总结:
对于多重背包一定要分成01背包和完全背包两部分,不然完全背包的部分仍然采用二进制拆分直接T上天。
#include<bits/stdc++.h>
#define int long long
using namespace std;int n,m,c;
//n种物品1 m种物品2 c背包容量
int v[10005],d[10005],w[10005];
//v体积 w权重 d数量
int t1[15],t2[15],t3[15];
int dp[10005];void onezeroPack(int weight,int volume){for(int i=c;i>=volume;i--){dp[i]=max(dp[i],dp[i-volume]+weight);}
}void complePack(int weight,int volume){for(int i=volume;i<=c;i++){dp[i]=max(dp[i],dp[i-volume]+weight);}
}signed main()
{scanf("%lld%lld%lld",&n,&m,&c);for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&v[i],&w[i],&d[i]);for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&t1[i],&t2[i],&t3[i]);for(int i=1;i<=n;i++){if(v[i]*d[i]>c) complePack(w[i],v[i]);else{//二进制拆分for(int j=1;j<=d[i];j<<=1){if(v[i]*j<=c){onezeroPack(w[i]*j,v[i]*j);d[i]-=j;}}//剩余无法继续拆的if(d[i]>0&&v[i]*d[i]<=c) onezeroPack(w[i]*d[i],v[i]*d[i]);}}for(int i=1;i<=m;i++){for(int j=c;j>=0;j--){for(int k=0;k<=j;k++){dp[j]=max(dp[j],dp[j-k]+t1[i]*k*k+t2[i]*k+t3[i]);}}}int res=0;for(int i=1;i<=c;i++){res=max(res,dp[i]);}printf("%lld\n",res);return 0;
}
学习记录 动态规划实时更新相关推荐
- Android 学习记录(持续更新)
Android 学习记录(持续更新) 1.AndroidManifest.xml 详解: http://www.jb51.net/article/73731.htm (AndroidManifest. ...
- 力扣学习记录(每日更新)
文章目录 引言 简单 力扣:1 两数之和 力扣:20 有效的括号 力扣:21 合并两个有序链表 力扣:22 括号生成 力扣:27 移除元素 力扣: 35 搜索插入位置 力扣:70 [爬楼梯](http ...
- Android Performance Patterns 系列视频学习记录(持续更新中)
系列文章旨在记录YouTube上谷歌发布的Android Performance Patterns系列视频,一共79个视频,每个视频也就几分钟.当然对于大部分安卓开发者来说,这些都是基础,可能你会说, ...
- 学习过程中遇到的一些电脑上的小BUG,非学习问题,实时更新
1.电脑桌面上有一个"关闭"字样的的小方框解决方法 电脑桌面上莫名出现有个"关闭"字样的小方框按钮,但无论怎么点击他都没有反应,而且会存在在各个页面上方 ,看着 ...
- excel操作技巧记录(实时更新)
1.连接符& A B C 效果 输入的公式 1 2 3 123 =A1&B1&C1 1 2 3 1-2-3 =A2&"-"&B1&& ...
- FORTRAN学习记录(持续更新)
FORTRAN: .f的后缀,表示固定格式: .f90的后缀,表示自由格式. 固定格式用'C'开头(必须在行首)表示注释+C后面用tab缩进,否则编译错误. 自由格式用'!'开头表示注释,!后面不用缩 ...
- 暑假做题记录【实时更新】
2017.7.6 POJ 3264[RMQ板子题,话说线段树也可以做,并且很简单] BZOJ 1303,1012,2257,2748,1088 比赛未做的三题补题 2017.7.7 待续....... ...
- Uuntu16 学习记录(持续更新中......)
Top Chunk: 概念:当一个chunk处于一个arena的最顶部(即最高内存地址处)的时候,就称之为top chunk. 作用:该chunk并不属于任何bin,而是在系统当前的所有free ch ...
- 超强实时跟踪系统PP-Tracking学习记录
PP-Tracting学习记录 超强实时跟踪系统PP-Tracking:飞桨AI Studio - 人工智能学习实训社区 (baidu.com) 目标检测 多目标跟踪 仅检测当前帧 物体的id信息可以 ...
最新文章
- day03-字符编码与转换
- CVS,GIT,Mercurial和SVN比较
- 拼图游戏 复制粘贴一个叫lemene的人的,这个人是c++博客的用户,我不是,怕以后找不到这篇文章,所以复制粘贴了。文中最后给出了原文链接连接...
- OpenCV-Python 雪花飘落特效
- elasticsearch负载均衡节点——客户端节点 node.master: false node.data: false 其他配置和master 数据节点一样...
- 如何让多端口网站用一个nginx进行反向代理实际场景分析
- mysql 2008 日_SQL2008 的 日期数据类型
- python网络编程---TCP客户端
- spring AOP 之一:spring AOP功能介绍
- Linux快速计算MD5和Sha1命令
- Centos7安装Docker教程
- 树莓派 pip安装mysql_树莓派 pip 手动安装
- 微信小程序,格式化千分位并保留两位小数
- 开启弹窗_PC端广告弹窗拦截
- android post 提交数据
- 三菱PLC定位控制1
- Excel 如何锁定表头
- yum安装ruby_Centos安装ruby
- 什么是数据挖掘,机器学习与数据挖掘主要有什么联系?
- softice 常用操作
热门文章
- Word技巧:在表格前添加空行
- 任何事,尽量从正面、善意的角度去解读,运气都不会太差
- php的算法是什么,算法是指什么
- 直面中国市场的新挑战,律商风险数据赋能车险智慧决策踏上新征程
- [人脸活体检测] 人脸活体检测简介
- 【解决】JSONDecodeError: Expecting property name enclosed in double quotes
- child计算机英语作文,Childhood的英语作文(精选8篇)
- 微信小程序(心理咨询类)
- 从爬取豆瓣影评到基于朴素贝叶斯的电影评论情感分析(下)
- 电脑中存储的文件怎么打印出来?