这里是目录

  • 写在读前:
  • 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. 基本概念:
    <1. 阶段:问题的子结构;
          阶段变量:阶段的具象化表示。
    <2. 状态:一个阶段具有若干个状态,多个状态构成一个子阶段;
          状态变量:状态的具象化表示。
    < 3.决策:将当前状态转移到下一个状态的行动;
          决策变量:用来描述决策;
          决策允许集合:当前状态可进行的所有决策的集合。
    <4. 策略:每个状态进行的决策纵向连接构成一种策略;
          最优策略:使状态最优化的策略。
  4. 过程描述:
    <1. 正推:即递推;
    <2. 倒推:即记忆化搜索。
  5. 解题思路:
    <1. 抽丝剥茧设计状态,<2. 寻找状态转移方程。

Part2 具体模型:

简单模型:

线性是指状态是线性分布的,中规中矩的将状态从0递推至n。

简单递推与数学:P1077 [NOIP2012 普及组] 摆花:

题目大意:共n种花,花的数量用一个长度为n的数组表示,a[i]代表第i种花有a[i]盆,需要使用这些花摆成一个长度为m的序列,同类花必须连续且摆放顺序按照花的下标大小排序,求不同摆放种类的数量。

状态设计:显然 dp[i][j] 代表使用前 i 种花摆 j 盆的不同序列数量。

状态转移:刚开始设计的是使用第一维花的种类数,每次给 dp[k][j-1] 补一个进行转移,即:
错误方程:dp[i][j]=∑k=1idp[k][j−1]错误方程:dp[i][j] = \sum_{k=1}^{i} dp[k][j-1] 错误方程:dp[i][j]=k=1∑i​dp[k][j−1]

但是由于a[i] 的限制,此方程无法记录每种花使用了多少盆,故会出现类似于“将多重背包做成完全背包”的错误。

那么就要考虑一种可以记录每种花使用数量的方程,我们使用第二维花的数量来进行转移,每次考虑给上一个状态,即已经使用了 i-1 种花的状态补花。即:

dp[i][j]=∑k=1max(0,j−a[i])dp[i−1][k]dp[i][j] = \sum_{k=1}^{max(0,j-a[i])} dp[i-1][k] dp[i][j]=k=1∑max(0,j−a[i])​dp[i−1][k]
最后再加上取余操作即可。

完整代码:

#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。

状态设计:开个map<PII,int> 类型的dp数组,PII为D和K的比例,int为PII出现的次数。
状态转移:由于想要划分必须保证每段比例相等,同时这个比例也和前缀中的总比例相同,那么记录有多少个相同比例递推即可。

完整代码:

#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交替出现的连续子串的最大数量。

状态设计:因为交替出现的01串只有两种情况,即为s[i]-'0'==i%2,和s[i]-'0'!=i%2的情况,dp[1][i]代表以第i位为区间右端点可以形成多少种第一种情况的交替子串,dp[0][i]代表第二种交替子串的数量。

状态转移:判断能不能续上上一个同类串即可,续的上的话该位置形成的贡献数量等于上一个+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石子归并

题目大意:给一个长度为n的序列,每次取相邻两个值相加的到一个新值,获得新值的得分,求最小得分。
状态设计:dp[l][r]代表区间 [l,r] 的最低得分。
状态设计:[l,r] 区间可以在(l,r)中任意分割成两个小区间,所以枚举dp[l,k]+dp[k+1,r]的和。

 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 能量项链

题意:环形区间dp模板,XX都能看出来,没什么好说的;
思路讲解:环形经典解决方法,开2倍的数组存环的权值;但是刚看题目的时候以为这道题限制了这种解决方法,就想着枚举每个环的起点(奇怪的思路增加了,其实就是看错题了= =。)
完整代码:

#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一定要学会逆向思维。

题目大意:给一个长度为n的序列,需要按规则依次选取全部元素。规则如下:
对一个将要被选取的元素,有一个区间,区间左边界为max(0,向左第一个已经被选取的元素的下标),右边界为min(n,向右第一个已经被选取的元素的下标),则需要付出 value(i) * (l-r-2)

思路讲解:因为一定要选取序列中的每个数,从已选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 合唱队

题目大意:将一个原始序列中的值不断插入到一个初始为空的目标序列中,对于原始序列中每个元素a[i],如果a[i]>a[i-1]则插入到目标序列最右端,否则插入到最左端。现在给你最终构造出来的目标序列,求有多少种原始序列可以构造出目标序列。

设计状态:首先分析目标序列是如何构成的,原始序列中的每个元素只有左端、右端两种方法插入到目标序列中,即目标序列是由上一个状态在左端插入或者在右端插入。那么状态和转移就都出来了。除了区间模型基本的二维以外,加一维是从上一个状态左边插入或者从右边插入。

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 提高组] 矩阵取数游戏

题目大意:给nm的矩阵,每次从n行中的每一行的首或尾各取一个数,获得
value∗2i(i代表第i次取数)value * 2^i (i代表第i次取数)value∗2i(i代表第i次取数)
的分数,求最高得分。
状态设计:虽然题目给的是n
m的矩阵,但是每行之间取数过程不同不会影响其他行的结果,所以可以n行独立进行dp,这样就可以转化成很裸的区间模型,dp[l][r]代表区间[l, r]的最大得分。
状态转移:
当前区间可以由 靠左的小区间向右扩展 或者靠右的小区间向左拓展 得来,两种值取最大值。

 //当前区间可以由 靠左的小区间向右扩展 或者靠右的小区间向左拓展 得来,两种值取最大值。dp[l][r]=max((dp[l+1][r]+a[i][l]),dp[l][r-1]+a[i][r])*2;

其他处理:

  1. 高精度用手写int128。
  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

先求出将一个空串转为B串的权值——区间dp
dp[l,r] 代表将区间 [l,r] 粘成B串的操作次数;使用合并区间的方法枚举[l,k]+[k+1,r]转移状态,但有一个情况需要判断,B[l]==B[r]?如果等的话,就相当于上一次操作可以从[l,r-1]延申到[l,r],可以使dp[l,r]=dp[l,r-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]);}}
}

再求在空白串转B串的基础上,使用A串能省多少次操作——线性dp
dp[i]代表将A[1,i]转为B[1,i]的最小操作次数;
枚举每次包括i的区间,算出当前状态的最优值,同第一步的特判一样,如果A[i]==B[i],那么dp[i]=dp[i-1]。

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
dp[l][r] 代表[l,r]的最大得分,那么被包括的大区间可以直接继承小区间的得分,前提是题目中提到的,没个单词的第一个字母不能重复使用,所以此处只要倒着判断计数,就可以避免重复(自己手动模拟一遍就能看出来了)。

求出每部分得分后,再用区间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元,可叠加使用,求商品最低可以减到多少钱。

思路讲解:这道题在01背包的基础上又做了限制,容量大于a,权重增加b,和普通的可行性问题间多了一个(a-b)的限制条件,那么这个限制条件会再筛去一部分物品不能放进背包,需要一个贪心策略选择在这种冲突下哪些物品放入背包最优,下面放上Kur1su大佬的证明:

完整代码:

#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 旅行商的背包

题目大意:
有两种物品,第一种种类数为n,第二种为m,背包重量为c。
第一种物品每种有di个,每个体积为vi,权重为wi。
第二种物品权重与体积的关系符合二项式 w = aiaiv + bi*v + c。
求背包最大权重。

思路分析:
第一种物品就是裸的多重背包;
第二种物品要枚举它在背包内的体积:
由于不同的体积拥有不同的权重,所以对于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;
}

学习记录 动态规划实时更新相关推荐

  1. Android 学习记录(持续更新)

    Android 学习记录(持续更新) 1.AndroidManifest.xml 详解: http://www.jb51.net/article/73731.htm (AndroidManifest. ...

  2. 力扣学习记录(每日更新)

    文章目录 引言 简单 力扣:1 两数之和 力扣:20 有效的括号 力扣:21 合并两个有序链表 力扣:22 括号生成 力扣:27 移除元素 力扣: 35 搜索插入位置 力扣:70 [爬楼梯](http ...

  3. Android Performance Patterns 系列视频学习记录(持续更新中)

    系列文章旨在记录YouTube上谷歌发布的Android Performance Patterns系列视频,一共79个视频,每个视频也就几分钟.当然对于大部分安卓开发者来说,这些都是基础,可能你会说, ...

  4. 学习过程中遇到的一些电脑上的小BUG,非学习问题,实时更新

    1.电脑桌面上有一个"关闭"字样的的小方框解决方法 电脑桌面上莫名出现有个"关闭"字样的小方框按钮,但无论怎么点击他都没有反应,而且会存在在各个页面上方 ,看着 ...

  5. excel操作技巧记录(实时更新)

    1.连接符& A B C 效果 输入的公式 1 2 3 123 =A1&B1&C1 1 2 3 1-2-3 =A2&"-"&B1&& ...

  6. FORTRAN学习记录(持续更新)

    FORTRAN: .f的后缀,表示固定格式: .f90的后缀,表示自由格式. 固定格式用'C'开头(必须在行首)表示注释+C后面用tab缩进,否则编译错误. 自由格式用'!'开头表示注释,!后面不用缩 ...

  7. 暑假做题记录【实时更新】

    2017.7.6 POJ 3264[RMQ板子题,话说线段树也可以做,并且很简单] BZOJ 1303,1012,2257,2748,1088 比赛未做的三题补题 2017.7.7 待续....... ...

  8. Uuntu16 学习记录(持续更新中......)

    Top Chunk: 概念:当一个chunk处于一个arena的最顶部(即最高内存地址处)的时候,就称之为top chunk. 作用:该chunk并不属于任何bin,而是在系统当前的所有free ch ...

  9. 超强实时跟踪系统PP-Tracking学习记录

    PP-Tracting学习记录 超强实时跟踪系统PP-Tracking:飞桨AI Studio - 人工智能学习实训社区 (baidu.com) 目标检测 多目标跟踪 仅检测当前帧 物体的id信息可以 ...

最新文章

  1. day03-字符编码与转换
  2. CVS,GIT,Mercurial和SVN比较
  3. 拼图游戏 复制粘贴一个叫lemene的人的,这个人是c++博客的用户,我不是,怕以后找不到这篇文章,所以复制粘贴了。文中最后给出了原文链接连接...
  4. OpenCV-Python 雪花飘落特效
  5. elasticsearch负载均衡节点——客户端节点 node.master: false node.data: false 其他配置和master 数据节点一样...
  6. 如何让多端口网站用一个nginx进行反向代理实际场景分析
  7. mysql 2008 日_SQL2008 的 日期数据类型
  8. python网络编程---TCP客户端
  9. spring AOP 之一:spring AOP功能介绍
  10. Linux快速计算MD5和Sha1命令
  11. Centos7安装Docker教程
  12. 树莓派 pip安装mysql_树莓派 pip 手动安装
  13. 微信小程序,格式化千分位并保留两位小数
  14. 开启弹窗_PC端广告弹窗拦截
  15. android post 提交数据
  16. 三菱PLC定位控制1
  17. Excel 如何锁定表头
  18. yum安装ruby_Centos安装ruby
  19. 什么是数据挖掘,机器学习与数据挖掘主要有什么联系?
  20. softice 常用操作

热门文章

  1. Word技巧:在表格前添加空行
  2. 任何事,尽量从正面、善意的角度去解读,运气都不会太差
  3. php的算法是什么,算法是指什么
  4. 直面中国市场的新挑战,律商风险数据赋能车险智慧决策踏上新征程
  5. [人脸活体检测] 人脸活体检测简介
  6. 【解决】JSONDecodeError: Expecting property name enclosed in double quotes
  7. child计算机英语作文,Childhood的英语作文(精选8篇)
  8. 微信小程序(心理咨询类)
  9. 从爬取豆瓣影评到基于朴素贝叶斯的电影评论情感分析(下)
  10. 电脑中存储的文件怎么打印出来?