DP一下,马上出发
简单DP
i.May I ask you for a dance(体舞课软广植入)
这题的状态转移方程为:dp[i][j]=max(dp[i-1][j-1]+a[i][j],dp[i][j-1]);(i<=j)
方法1:预处理:将i==j的情况提取出来,因为当i==j的时候,序列顺序是固定的,只能从1选到i(感谢zzdl)
方法2:将i>=j的情况全部赋值为-inf,这样在选取的时候就能够保证i>=j不会被选取
方法3:好感度可能为负值,范围为-100~100;因为dp初始化为0,所以可能会丢失好感度为负数的情况,所以预处理的时候将好感度+100,全部变成正数进行讨论
#include<bits/stdc++.h> using namespace std; ][], dp[][]; int n, m; ; int main(){ while(scanf("%d%d",&n,&m)!=EOF){ memset(a, , sizeof(a)); ; i <= n;i++) ; j <= m;j++){ scanf("%d", &a[i][j]); //方法3:a[i][j]+=100; } memset(dp, , sizeof(dp)); //3选1 //方法1: //for (int i = 1; i <= n;i++){ // for (int j = 1; j <= i;j++){ // dp[i][i] += a[j][j]; // } //} //方法2: ; i <= n;i++) ; j <= i;j++) dp[i][j] = -inf; ; i <= n; i++) ; j <= m; j++) if(i<=j)//这句可以不加 dp[i][j] = max(dp[i - ][j - ] + a[i][j], dp[i][j - ]); ]; ; j <= m;j++) ans = max(ans, dp[n][j]); //方法3:ans-=100*n; printf("%d\n", ans); } system("pause"); ; }
ii.秘密交易
题意:给定一串由“D”“G”组成的序列
发牌权首先掌握在G手中,每次的决策为:
1.一个人帮另一个人出一张牌,代价为1
2.掌控权转移,代价为1
有掌控权的人可以连续出牌不计代价
这题可以贪心,但我WA了
用DP写,dp[i][0]表示第i次掌控权在D手中,dp[i][1]表示第i次掌控权在G手上
#include<bits/stdc++.h> using namespace std; ][]; int main(){ int t;cin>>t; while(t--){ ; ]; scanf(); ); ]==]=='G') ans--; s[] = 'G'; s[] = 'G';//可省略 s[n+] = 'G'; s[n+] = 'G';//可省略 memset(dp, 0x3f, sizeof(dp)); dp[][]=; dp[][] = ; ; i <= n + ;i++){ if(s[i]=='D'){ dp[i][] = min(dp[i - ][], dp[i - ][] + ); dp[i][] = min(dp[i - ][]+, dp[i - ][]+); } if(s[i]=='G'){ dp[i][] = min(dp[i - ][], dp[i - ][] + ); dp[i][] = min(dp[i - ][] + , dp[i - ][]+); } } cout << min(dp[n + ][], dp[n + ][]) << endl; } system("pause"); ; }
iii.HDU1024
题意:给定长度为n的序列,找出m个子序列,使总和最大
选择第i个数,使它存在于第j个子序列当中
存在于第j个子序列:
情况1:作为第j个子序列的第一个元素
情况2:作为第j个子序列中间的/最后的元素
则状态转移方程为:
dp[i][j]=max(dp[i-1][j],max(dp[k][j-1]])+a[i];(1<=k<=i-1)
这个式子的后面这一部分表示:
选定了j-1个序列,它们的最大值,在i-1位置之前的最大值
我个人认为这个k的范围应该是:j-1<=k<=i-1
那么可以降为:
dp[j]=max(dp[j-1],pre[j-1])+a[j];
注意pre的求取方法
pre表示的是选取i-1段的最大值,更新以后变成选取i段的最大值。
所以只可以更新到第pre[j-1]而不是pre[j],否则在dp[j+1]的更新时会用到pre[j],而此时的pre[j]表示的是选取i段的了
#include<bits/stdc++.h> using namespace std; #define sys system("pause") ; const int inf = 0x3f3f3f3f; int a[maxn], pre[maxn], dp[maxn],m,n,ans; int32_t main(){ while(scanf("%d%d",&m,&n)!=EOF){ ;i<=n;i++){ scanf("%d", &a[i]); dp[i] = pre[i] = ; } dp[] = ; pre[] = ; ;i<=m;i++){ ans = -inf; for (int j = i; j <= n;j++){ dp[j] = max(dp[j - ] + a[j], pre[j-]+a[j]); pre[j - ] = ans; ans = max(ans, dp[j]); } } printf("%d\n", ans); } //sys; ; }
iiii.CF Ayoub and Lost Array
题意:
给定一个闭区间,[l,r],要在里面选n个数,保证n个数的和为3的倍数
思路:数论+dp
关于数论部分:
要让n个数的和为3,比如现在有两个数,a和b
有一个简单的前置技能的式子,那就是:
((a mod n)+(b mod n) )mod n==(a+b)mod n;
减法和乘法同理
那么能够满足a+b=3*k,即(a+b)%3==0 也就是
[(a%3)+(b%3) ]%3==0
如果a%3==1,那么b%3为2;
如果a%3==0,那么b%3为0;
如果a%3==2,那么b%3为1
这样才能保证两个数相加能够被3整除
关于dp部分
这里的dp我们用一个二维数组表示,dp[N][3];
对于dp[i][j];
i代表目前已经选择了i个数相加,j表示他们的和的余数目前是0或1或2;
dp数组的值代表选择i个数相加,并且它们的和的余数为0/1/2有多少种;
在for循环里,每一次i++,我们都要选取一个新的数加进我们选择的数组,最后直到我们已经选完了n个数;
那么每次选的时候,比如说我们现在是
dp[i][2] = ( (dp[i - 1][0] * n2)%mod + (dp[i - 1][1] * n1)%mod + (dp[i - 1][2] * n0)%mod )%mod;
对于这个式子,那么就是我们现在已经有i-1个数已经排列好了,并且之前有多少种可能性已经计算过了,现在想再加一个数进去,构成一个长度为i的,和的余数为2的数组;
现在我们想在原本的i-1序列中加一个数,使得和的余数为2,
那么对于dp[i-1][0],要乘上余数为2的数的数量,也就是dp[i-1][0]*n2,这样才可以得到一个余数为2的数
#include<bits/stdc++.h> using namespace std; typedef long long ll; //#define int long long ; ; ll n, dp[N][],l,r,n0,n1,n2; int main(){ scanf("%ld%ld%ld", &n,&l,&r); n0= n1 = n2 = (r - l + ) / ; ll num = (r - l + ) % ; ){ ==) n2++; ==) n1++; ==) n0++; } ){ ==){ n0++; n1++; } ==){ n1++; n2++; } ==){ n0++; n2++; } } dp[][] = n0; dp[][] = n1; dp[][] = n2; ; i <= n; i++){ dp[i][] = ( (dp[i - ][] * n0)%mod + (dp[i - ][] * n2)%mod + (dp[i - ][] * n1)%mod )%mod; dp[i][] = ( (dp[i - ][] * n1)%mod + (dp[i - ][] * n0)%mod + (dp[i - ][] * n2)%mod )%mod; dp[i][] = ( (dp[i - ][] * n2)%mod + (dp[i - ][] * n1)%mod + (dp[i - ][] * n0)%mod )%mod; } cout << dp[n][]; //system("pause"); ; }
iiiii.codeforces429B working out
题目描述
夏天要到了,小西和小瓜决定去健身。健身房是一个n行m列的矩阵。 a[i][j]代表在第i行第j列,可以消耗的卡路里。 小西从第1行第1列出发,要到(n,m);小西可以从a[i][j],走到a[i+1][j],或者a[i][j+1]; 小瓜从(n,1)出发,要到(1,m);小瓜可以从a[i][j]出发,走到a[i-1][j],或者a[i][j+1]; 由于小西和小瓜要聚在一起发张自拍,他们必须在健身房的某一行,某一列相遇。由于他们没有好好健身,所以在自拍地点的卡路里消耗不计入总卡路里消耗值。 如果小西和小瓜中任何一个人完成了健身,则健身结束。你的任务是求出小西和小瓜可以消耗的最大总卡路里值。 另外,他们的健身速度不同,所以可以走过的路线长度也不同。
输入输出格式
输入格式:
第一行包含两个整数,n,m(3<=n,m<=1000),代表健身房的行、列数,接下来的i行j列代表a[i][j],即在第i行,j列可以燃烧的卡路里。
输出格式:
输出一个整数:即他们可能消耗的总卡路里的最大值。
说明
对于样例1,小西可以选择: a[1][1]→a[1][2]→a[2][2]→a[3][2]→a[3][3] . 小瓜可以选择: a[3][1]→a[2][1]→a[2][2]→a[2][3]→a[1][3] .
预处理:
定义一个三维数组,dp[i][x][y]
表示在(x,y)处,分别向(1,1),(1,m),(n,1),(n,m)点可以消耗的最大热量
i=1,2,3,4
注意预处理时循环的顺序。
取最大值:
假设第i,j点是小西和小瓜相遇的点,求最大值
注意循环的顺序
#include<bits/stdc++.h> using namespace std; #define int long long ][], dp[][][], sum, n, m; int32_t main(){ int n, m; scanf("%I64d%I64d", &n, &m); ;i<=n;i++) ;j<=m;j++) scanf("%I64d", &a[i][j]); ;i<=n;i++){ ;j<=m;j++) dp[][i][j] = max(dp[][i - ][j], dp[][i][j - ]) + a[i][j]; ;j--) dp[][i][j] = max(dp[][i - ][j], dp[][i][j + ]) + a[i][j]; } ;i--){ ;j<m;j++) dp[][i][j] = max(dp[][i + ][j], dp[][i][j - ]) + a[i][j]; ;j--) dp[][i][j] = max(dp[][i + ][j], dp[][i][j + ]) + a[i][j]; } ;i<n;i++) ;j<m;j++){ sum = max(sum, dp[][i - ][j] + dp[][i + ][j] + dp[][i][j - ] + dp[][i][j + ]); sum = max(sum, dp[][i][j - ] + dp[][i][j + ] + dp[][i + ][j] + dp[][i - ][j]); } printf("%I64d\n", sum); //system("pause"); ; }
6.POJ2479Maximum sum/POJ2593Max Sequence
在一个序列里取两个最大连续子序列
#include<iostream> #include<cstdio> #include<cstring> using namespace std; ], a[]; int n, T, sum, t, ans; int main(){ int T; cin >> T; while(T--){ cin >> n; memset(dp, , sizeof(dp)); ; i <= n; i++) scanf("%d", &a[i]); sum = , t = -, ans = -; ; i <= n;i++){ sum += a[i]; t = max(sum, t); dp[i] = t; ) sum = ; } sum = , t = -; ;i--){ sum += a[i]; t = max(sum, t); ans = max(ans, t + dp[i - ]); ) sum = ; } printf("%d\n", ans); } //system("pause"); ; }
状压DP
i.HDU1074
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define int long long #define sys system("pause") #define scan(n) scanf("%lld", &(n)) #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define scannn(a,b,c) scanf("%lld%lld%lld",&(a),&(b),&(c)) #define prin(n) printf("%lld", (n)) #define prins(n) printf("%s\n", (n)) #define ff first #define ss second #define mp make_pair #define pb push_back #define pii pair<int,int> #define mem(a) memset(a,0,sizeof(a)) #define fo(a, b) for (int i = (a); i <= (b);i++) #define REP(i,n) for(int i = 1; i <= (n); i++) << ); int n, tim[maxn], dp[maxn], pre[maxn], c; //c==cost,tim数组表示状态index时所在的时间 ][]; struct node{ int d;//deadline int c;//cost } a[]; void print(int x){ if(!x) return; print(x - ( << pre[x])); cout << s[pre[x]] << endl; } int32_t main(){ int t; scan(t); while(t--){ scan(n); fo(,n-) scanf("%s %d %d", &s[i], &a[i].d, &a[i].c); memset(dp, 0x3f, sizeof(dp));//刚开始初始化为无穷,为了求最小值 memset(pre, , sizeof(pre)); << n)-;//所有状态表示为S dp[] = ; ; i <= S;i++){ ; j >=;j--){ <<j))) continue; << j);//把j状态删掉,k比i小 c = tim[k] + a[j].c - a[j].d; ) c = ;//dp里面存的是花费的价值,我们需要的是最小值 if(dp[k]+c<dp[i]){//说明选择k状态 dp[i] = dp[k] + c; pre[i] = j;//pre里面保存的是单个点,用于输出 tim[i] = tim[k] + a[j].c; } } } cout << dp[S] << endl; print(S); } sys; ; }
总状态个数为S=(1<<n)-1
注意:<<运算符的优先级小于==
子集枚举
枚举所有点对:
;i<n;i++) ;S<(<<n);S++){ d[i][S]=INF; ;j<i;j++) <<j)) dp[i][S]=max(dp[i][S],dist(i,j)+dp[i-][S^(<<i)^(<<j)]; }
S表示状态,i和j表示不同的点,并且i和j的枚举不会重复,因为保证了j<i
对于每一个i,都有1<<n个S可以枚举
例如n=6
状态可以是:
000000
000001
000011
000111
010101
.......
1111111
S^(1<<i)^(1<<j)表示i,j不在该集合内
S&(1<<i)==true 表示第i位元素存在
DAG问题
123
数字三角形
HDU1176
gameboy接馅饼
按照题目给的情况来就行了,从底往上更新
#include<bits/stdc++.h> using namespace std; typedef long long ll; ; ]; ]; int n,x,t,maxt; int trimax(int a,int b,int c){ return max(a,max(b,c)); } int main(){ while(scanf("%d",&n)!=EOF&&n){ maxt=;//* memset(dp,,sizeof(dp));//* memset(pile,,sizeof(pile));//* while(n--){ scanf("%d %d",&x,&t); pile[t][x]++; maxt=max(maxt,t); } ;i<;i++)//* dp[maxt][i]=pile[maxt][i]; ;i>=;i--){ ;j<;j++){ ) dp[i][j]=max(dp[i+][j],dp[i+][j+])+pile[i][j]; ][j-],dp[i+][j],dp[i+][j+])+pile[i][j]; } } printf(][]); } ; }
CF731E funny game
Once upon a time Petya and Gena gathered after another programming competition and decided to play some game. As they consider most modern games to be boring, they always try to invent their own games. They have only stickers and markers, but that won't stop them.
The game they came up with has the following rules. Initially, there are n stickers on the wall arranged in a row. Each sticker has some number written on it. Now they alternate turn, Petya moves first.
One move happens as follows. Lets say there are m ≥ 2 stickers on the wall. The player, who makes the current move, picks some integer k from 2 to m and takes k leftmost stickers (removes them from the wall). After that he makes the new sticker, puts it to the left end of the row, and writes on it the new integer, equal to the sum of all stickers he took on this move.
Game ends when there is only one sticker left on the wall. The score of the player is equal to the sum of integers written on all stickers he took during all his moves. The goal of each player is to maximize the difference between his score and the score of his opponent.
Given the integer n and the initial sequence of stickers on the wall, define the result of the game, i.e. the difference between the Petya's and Gena's score if both players play optimally.
Input
The first line of input contains a single integer n (2 ≤ n ≤ 200 000) — the number of stickers, initially located on the wall.
The second line contains n integers a1, a2, ..., an ( - 10 000 ≤ ai ≤ 10 000) — the numbers on stickers in order from left to right.
Output
Print one integer — the difference between the Petya's score and Gena's score at the end of the game if both players play optimally.
题意:有一个序列,两个同学每次选一个前缀和,得到一个新分数,放回原序列最前端,然后希望两位同学的分值相差最大
这里涉及到一个先手后手最优的问题
#include<bits/stdc++.h> using namespace std; typedef long long ll; ; //a tower~~ int a[N]; int dp[N]; int main() { ios_base::sync_with_stdio(); cin.tie(); cout.tie(); int n;cin>>n; cin>>a[]; ;i<n;i++){ cin>>a[i]; a[i]+=a[i-]; } dp[n-]=a[n-];//sum ;i>=;i--){ dp[i]=max(dp[i+],a[i]-dp[i+]); } cout<<dp[]; ; }
HDU数塔
#include<bits/stdc++.h> using namespace std; #define int long long #define SYS system("pause"); #define IOS ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0); ; ][],dp[][],ans,n,h; int32_t main(){ IOS cin >> n; while(n--){ memset(dp, , sizeof(dp)); memset(a, , sizeof(a)); cin >> h; ; i <= h;i++) ; j <= i;j++) cin >> a[i][j]; ; i <= h;i++) dp[h][i] = a[h][i]; ; i >= ; i--){ ; j <= i;j++){ dp[i][j] = max(dp[i + ][j], dp[i + ][j + ]) + a[i][j]; } } cout << dp[][] << endl; } //SYS ; }
最长上升子序列
i.HDU1160
#include<bits/stdc++.h> using namespace std; //最长上升子序列+输出的操作 struct node{ int w, s, ind; bool operator<(const node &n)const{ if(w>n.w) return true; else if(w==n.w) return s < n.s; else return false; } } m[]; ],dp[]; int main(){ while(scanf("%d%d",&m[ind].w,&m[ind].s)!=EOF){ m[ind].ind = ind+; ind++; } int n = ind; sort(m, m + n); //for (int i = 0; i < n;i++) // dp[i] = 1; memset(pre, -, sizeof(pre)); ; i < n;i++){ dp[i] = ;//不许忘 ; j < i; j++){ if(m[j].w>m[i].w&&m[j].s<m[i].s){ ){ dp[i] = dp[j] + ; pre[i] = j;//*** } } } } ,pos=; ; i < n;i++){ if(maxdp<dp[i]){ maxdp = dp[i]; pos = i; } } cout << dp[pos] << endl; ){ cout << m[pos].ind<<endl; pos = pre[pos]; } system("pause"); ; }
背包
背上我的小书包~
背包九讲 very good~
01背包/完全背包/多重背包中的顺序/逆序
对于01背包
内循环是逆序
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
保证了每一次取的dp[j-w[i]]是选取前i-1个的最优解(上一层)
对于完全背包
内循环是顺序
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
这里的dp[j-w[i]]已经被更新为前i个的最优解了(本层)
多重背包
内循环是顺序,然后在选取i个的里面,按照数量再展开
神奇代码在这里:
#include <stdio.h> #include <string.h> #include <iostream> #include <stack> #include <queue> #include <vector> #include <algorithm> #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; ];//价值 ];//重量 ];//数量 ]; int v,m; void bag01(int c,int w)//01背包 { int i; for(i=v; i>=c; i--) { if(dp[i]<dp[i-c]+w) { dp[i]=dp[i-c]+w; } } } void bagall(int c,int w)//完全背包 { int i; for(i=c; i<=v; i++) { if(dp[i]<dp[i-c]+w) { dp[i]=dp[i-c]+w; } } } void multbag(int c,int w,int n)//多重背包 { if(c*n>=v) { bagall(c,w); return ; } ; while(k<=n) { bag01(k*c,k*w); n=n-k; k=k*; } bag01(n*c,n*w); } int main() { int t; scanf("%d",&t); while(t--){ mem(dp,); scanf("%d%d",&v,&m); ; i<m; i++) scanf("%d%d%d",&value[i],&weight[i],&num[i]); ; i<m; i++) multbag(value[i],weight[i],num[i]); printf("%d\n",dp[v]); } ; }
一段废话,以前博客里写的
背上我的小书包系列讲座~
最最最简单的背包长什么样:有一个总容量t
没了.....
题目里给的条件:
有m个物体,放进去
物体m的属性是:w[i],v[i],w表示重量,v表示价值//weight&&value
情景:
明天去春游,有一个承重量最大为t的小书包~~,然后现在老妈买了好多零食
发现带不了那么多,然后就要选择最喜欢的零食加进去;
最后使得“啊哈哈哈明天我春游了我带的都是我喜欢吃的小零食好开心的”这个开心值尽可能大,这是一个求最优解的问题,答案只能有一个。
但是呢,每个小零食的重量是不一样的,可能那个东西很重,但是我喜欢吃,特别特别喜欢,特别特别特别喜欢吃,不让我带这个我就哭给你看。
然后呢,我们就要写一个状态转移方程,来看看这个零食是不是已经喜欢到非带不可的地步了
就是这样吧
如果还要分析的话,那么就是,状态就是:这个小零食带不带,此时包包里还能装多少东西,此时得到的最大开心值是多大呢?
然后我们对于这个背包就是定义一个数组:dp[i]
综述完毕,话都在题里了!干杯!
01背包
物品数量有限的情况:
P1060 开心的金明
题意:一个最大承重小于n的,最大物品数为m的背包
给出m个物体的价格和“重要度“
要求:不超过背包限制时,能够得到的最大的 “价格*重要度” 的和
#include<bits/stdc++.h> using namespace std; #define int long long #define sys system("pause") #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define prin(n) printf("%lld", (n)) #define fo(a, b) for (int i = (a); i <= (b);i++) ; int dp[maxn], v[maxn], w[maxn]; int32_t main(){ int n, m;//总钱数要少于n、、物体个数为m不一定选完 scann(n, m); fo(, m) scann(w[i], v[i]); ; i <= m;i++)//选择1~m个物体,dp表示选取了i个时的最优解 for (int j = n; j >= w[i];j--)///价值要保证在范围内 dp[j] = max(dp[j],dp[j - w[i]] + w[i] * v[i]);//选取或者不选,由第i-1层转移而来 prin(dp[n]); sys; ; }
P1048采药
#include<bits/stdc++.h> using namespace std; #define int long long #define sys system("pause") #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define prin(n) printf("%lld", (n)) #define fo(a, b) for (int i = (a); i <= (b);i++) ; int dp[maxn], w[maxn], v[maxn]; int32_t main(){ int t, m; scann(t, m);//时间&数目 fo(, m) scann(w[i], v[i]); fo(,m){ for (int j = t; j >= w[i];j--) dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } prin(dp[t]); sys; ; }
P1049装箱问题
#include<bits/stdc++.h> using namespace std; #define int long long #define sys system("pause") #define scan(n) scanf("%lld", &(n)) #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define prin(n) printf("%lld", (n)) #define fo(a, b) for (int i = (a); i <= (b);i++) ; int a[maxn],dp[maxn]; int32_t main(){ int v,n; scann(v, n); fo(,n) scan(a[i]); //思路就是,装不装这个物体进来 //那么枚举物体数量,看看加不加入这个背包 //但是要注意,反向写,找到能加入的最大值,不要直接按照题目进行模拟 fo(,n){ for (int j = v; j >= a[i];j--){ dp[j] = max(dp[j], dp[j - a[i]] + a[i]); } } prin(v - dp[v]); sys; ; }
物品数量无限
P1616 疯狂的采药
刚不是有个同学采药嘛
我现在得知他已经疯了
于是——疯狂的采药!!!!
#include<bits/stdc++.h> using namespace std; #define int long long #define sys system("pause") #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define prin(n) printf("%lld", (n)) #define fo(a, b) for (int i = (a); i <= (b);i++) ; int w[maxn], v[maxn], dp[maxn]; int32_t main(){ int t,m; scann(t, m); fo(, m) scann(w[i],v[i]); fo(, m) for (int j = w[i]; j <= t; j++)//我们要更新的不是w[i],w[i]*2,~~~w[i]*n,因为每加入一个新的物品,都会重新更新一下背包在改状态下的数量 dp[j] = max(dp[j], dp[j - w[i]] + v[i]); prin(dp[t]); sys; ; }
只有第14行有那么一点点区别
让我们把它们截出来(疯狂有时候只在一行之间)
采药: fo(,m){ for (int j = t; j >= w[i];j--) dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } 疯狂采药: fo(, m) { for (int j = w[i]; j <= t; j++) dp[j] = max(dp[j], dp[j - w[i]] + v[i]); }
这个前面已经说过原因了
二维和一维~
P1064小A点菜
现在用这道题来尝试着解释一下
dp的一维和二维之间的关系
(有点饿了)
题意:小A有m块钱,现在点菜,一样最多点一道,现在想把钱花光光(一点开源节流的意识都没有)
然后问有多少种方案
如果是一维的话,还是和刚才一样,我们之前的题目用的都是一维的背包
代码如下:
#include<bits/stdc++.h> using namespace std; #define int long long #define sys system("pause") #define scan(n) scanf("%lld", &(n)) #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define prin(n) printf("%lld", (n)) #define fo(a, b) for (int i = (a); i <= (b);i++) ; int dp[maxn], w[maxn];//重量 int32_t main(){ int n, m;//m是背包容量 scann(n,m); dp[] = ;//*** fo(, n) scan(w[i]); fo(, n) for (int j = m; j >= w[i]; j--) dp[j] += dp[j - w[i]];//加上-->不加这个东西的方法 prin(dp[m]); sys; ; }
二维+分类讨论:
#include<bits/stdc++.h> using namespace std; #define int long long #define sys system("pause") #define scan(n) scanf("%lld", &(n)) #define scann(n, m) scanf("%lld%lld", &(n), &(m)) #define prin(n) printf("%lld", (n)) #define fo(a, b) for (int i = (a); i <= (b);i++) ; int a[maxn], dp[maxn][maxn]; int32_t main(){ int n, m; scann(n, m); fo(, n) scan(a[i]); fo(,n){//前i道菜 ; j <= m;j++){//钱 if(j==a[i]) dp[i][j] = dp[i - ][j] + ; if(j>a[i]) dp[i][j] = dp[i - ][j] + dp[i - ][j - a[i]]; if(j<a[i]) dp[i][j] = dp[i - ][j]; } } prin(dp[n][m]); sys; ; }
二维和一维的区别就是:
二维的dp[i][j]
这个i表示的是只选取前i个东西时的状态
但是其实,如果用1维的话,我们也是能够保证每次扫一遍的时候可以把方法数更新一遍
怎么说呢
就是我们一维的转移方程是:
dp[j]+=dp[j-w[i]];
这个时候,dp代表的是,重量为j的时候,方案数是多少
TUT我说不来了,下次再说
补充:二维和一维的区别应该是在空间复杂度上,时间上已经没有办法优化了
然后刚才搞懂了一个非常好玩的东西
从采药和疯狂采药这两个题入手
它们一个叫01背包,一个叫完全背包
01背包只存在这一个选还是不选
我们现在来看它的二维状态转移方程:
dp[i][v]==max{dp[i-1][v],dp[i-1][v-c[i]]+w[i]}
而这个方程是套在哪两个循环里面的呢?
for(int i=1;i<=m;i++){
for(int j=n;j>=c[i];j--){
dp[i][j]==max{dp[i-1][j],dp[i-1][j-c[i]]+w[i]}
}
}
也就是说,第i个选不选,是选取完i-1个之后,这个第i个物体是不放,还是让其他的物体腾出c[i]的空间,(腾出的意思就是:找到容量为v-c[i]的那个状态,把第i个物体放进去呢?
所以对于j循环,我们的循环是n~c[i]
因为我们的dp[i][j]状态 是由dp[i-1][j]//dp[i-1][j-c[i]]推出来的
但是对于完全背包就不一样了
它的状态是由它自己的那个推来的
我自己YY出来的公式是
dp[i][v]=max{dp[i][v-c[i]]+w[i],dp[i-1][v]}
第一个那个是它自己~,第二个是不选取这个东西
所以它的j循环是 c[i]~n
所以,区别就是,顺序&&逆序
完全背包(恰好背包)
写一个完全背包的伪代码;
初始化:F[]=-1;
F[0]=0;
FOR i (1,n)
FOR j (m,0)
if F[]!=-1
DO:
DP[j]=MAX(DP[j],DP[j-c[i]]+w[i]);
要注意的就是该容量可不可取
贪心与动态规划的结合
这是在浙大校赛中遇到的两道题目,都是01背包,但是融入了贪心的思想
对于01背包,存在“选”与“不选”两个状态,那么选择的顺序呢?
分析这道题目的条件就会发现,需要一个简单的排序。这样计算的时候,物品体积就是排序后体积的前缀和
并且这道题目,由于有两个k值,所以定义一个二维dp数组,表示选取k1数组的前i个和k2数组的前j个
这道题可以不对不合情况的dp标记-1,不影响结果
#include <bits/stdc++.h> using namespace std; #define int long long ; int k1, k2, c, n, m; int a[maxn],b[maxn],dp[maxn][maxn]; int sum1[maxn], sum2[maxn], ans; int32_t main(){ int T; cin >> T; while(T--){ memset(a, , sizeof(a)); memset(b, , sizeof(b)); ans = ; scanf("%lld%lld%lld", &k1, &k2, &c); scanf("%lld%lld", &n, &m); ; i <= n;i++) scanf("%lld", &a[i]); ; i <= m;i++) scanf("%lld", &b[i]); sort(a+,a++n); sort(b + , b + + m); sum1[] = a[]; sum2[] = b[]; ; i <= n;i++) sum1[i]= sum1[i - ] + a[i]; ; i <= m;i++) sum2[i] = sum2[i - ] + b[i]; ; i <= n;i++){ if(sum1[i]<=c){ dp[i][] = dp[i - ][] + (c - sum1[i]) * k1; ans = max(dp[i][], ans); } //else // dp[i][] = -; } ; i <= m;i++){ if(sum2[i]<=c){ dp[][i] = dp[][i - ] + (c - sum2[i]) * k2; ans = max(dp[][i], ans); } //else // dp[][i] = -; } ; i <= n;i++){ ; j <= m;j++){ int t = sum1[i] + sum2[j]; if(t<=c){ dp[i][j] = max(dp[i - ][j] + (c - t) * k1, dp[i][j - ] + (c - t) * k2); ans = max(ans, dp[i][j]); } } } printf("%lld\n", ans); } //system("pause"); ; }
ZOJ3958
这题也是一样
观察数据,发现,Ci<=100,啊哈哈哈哈
所以Ci的和相同时,只需要看Hi的和的大小,明确了这点,小书包背上~
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 int h[600], c[600], dp[50020]; 5 int n, ans, sum; 6 int32_t main() 7 { 8 int T;cin>>T; 9 while(T--){ 10 sum = 0, ans = 0; 11 scanf("%lld", &n); 12 memset(dp, 0, sizeof(dp)); 13 for (int i = 1; i <= n;i++){ 14 scanf("%lld%lld", &h[i], &c[i]); 15 sum += c[i]; 16 } 17 //initially select all of them; 18 for (int i = 1; i <=n;i++){ 19 for (int j = sum; j >= c[i];j--){ 20 dp[j] = max(dp[j], dp[j - c[i]] + h[i]); 21 } 22 } 23 for (int i = 1; i <= sum;i++){ 24 ans = max(dp[i] * dp[i] - i * dp[i] - i * i, ans); 25 } 26 printf("%lld\n", ans); 27 } 28 //system("pause"); 29 return 0; 30 }
分组背包
P1064金明的预算方案
这道题的标准解法应该是:
对于每一个物品组进行完全背包“预处理”,这个方法始用于物品组的附件数量任意,我写了一遍,有点不熟练。
这道题直接写也可以,因为附件最多只有3个
#include<bits/stdc++.h> using namespace std; ]; struct node{ int v, p, q,c; } a[], b[][]; ]; int main(){ int n, m; cin >> n >> m; ; i <= m;i++){ scanf("%d%d%d", &a[i].v, &a[i].p, &a[i].q); a[i].c = a[i].v * a[i].p; if(a[i].q){ int t = a[i].q; cnt[t]++; b[t][cnt[t]].v = a[i].v; b[t][cnt[t]].c = a[i].c; } } ; i <= m;i++){ ) for (int j = n; j >= a[i].v;j--){ dp[j] = max(dp[j], dp[j - a[i].v] + a[i].c); ].v) dp[j] = max(dp[j], dp[j - a[i].v - b[i][].v] + a[i].c + b[i][].c); ].v) dp[j] = max(dp[j], dp[j - a[i].v - b[i][].v] + a[i].c + b[i][].c); ].v+b[i][].v) dp[j] = max(dp[j], dp[j - a[i].v - b[i][].v - b[i][].v] + a[i].c + b[i][].c + b[i][].c); } } cout << dp[n]; //system("pause"); }
区间DP
综述
回忆一下,区间DP是以区间长度从小到大为主导的
可以枚举区间长度,也可以直接利用i,j循环遍历
i.石子合并
来瞻仰一下这个剪贴板: http://xlorpaste.cn/xRdHOE
注意这个石子堆是环状的&&千万要注意实现的方法,别搞错了,但这个已经不属于DP的范畴了~
方法1:
#include<bits/stdc++.h> using namespace std; ; ][], dp_min[][], sum[]; ]; int main(){ cin >> n; ;i<=n;i++){ cin >> a[i]; a[i+n]=a[i]; } ; i <= * n;i++) sum[i] = sum[i - ] + a[i]; ; len <= n;len++) ; i <= * n; i++){ int j = i + len; *n){ dp_min[i][j] = ; for (int k = i; k <j; k++){ dp_min[i][j] = min(dp_min[i][j], dp_min[i][k] + dp_min[k + ][j] + sum[j] - sum[i - ]); dp_max[i][j] = max(dp_max[i][j], dp_max[i][k] + dp_max[k + ][j] + sum[j] - sum[i - ]); } } } ; i <= n;i++){ min1 = min(min1, dp_min[i][i + n-]); max1 = max(max1, dp_max[i][i + n-]); } printf("%d\n%d", min1, max1); system("pause"); ; }
方法2:
#include<bits/stdc++.h> using namespace std; ; ][], dp_min[][], sum[]; ]; int main(){ cin >> n; ;i<=n;i++){ cin >> a[i]; a[i+n]=a[i]; } ; i <= * n;i++) sum[i] = sum[i - ] + a[i]; * n - ; i >= ; i--){ ; j <= * n;j++){ dp_min[i][j] = ; for (int k = i; k < j; k++){ dp_min[i][j] = min(dp_min[i][j], dp_min[i][k] + dp_min[k + ][j] + sum[j] - sum[i - ]); dp_max[i][j] = max(dp_max[i][j], dp_max[i][k] + dp_max[k + ][j] + sum[j] - sum[i - ]); } } } ; i <= n;i++){ min1 = min(min1, dp_min[i][i + n-]); max1 = max(max1, dp_max[i][i + n-]); } printf("%d\n%d", min1, max1); system("pause"); ; }
记住大佬说的:DP是以区间长度优先
第一遍的那个循环有点问题
第一遍的代码在此:http://xlorpaste.cn/Vtx337
为什么不直接贴上来?
因为我喜欢这个paste *OvO*
猜猜哪个是对的,哪个是错的?
完整版,一个正确一个错误
自己画图就懂了
http://xlorpaste.cn/xRdHOE
每一次得到的就必须是最优解,不能一遍一遍地更新
ii.加分二叉树
两个方法:
WAY1:
#include<bits/stdc++.h> #define int long long #define sys system("pause") using namespace std; #define N 32 int dp[N][N], ans,n,root[N][N],v[N]; void build(int n){ ; i <= n;i++){ dp[i][i-] = ;//题目指出,空树加分为1 dp[i][i + ] = ;//题目指出,空树加分为1 dp[i][i] = v[i];//叶子节点 } } void dfs(int l,int r){//前序输出 if(l>r) return; if(l==r){ printf("%lld ",l); return; } printf("%lld ", root[l][r]);//根 dfs(l, root[l][r]-);//左,由图可知,找左子树只需要root[l][r]-1 dfs(root[l][r] + , r);//右 } void init(){ scanf("%lld", &n); ; i <= n;i++){ scanf("%lld", &v[i]); } } int32_t main(){ init(); build(n); //dp1: ; j <=n;j++) ; i >= ; i--) for (int k = i; k <= j; k++) ] * dp[k + ][j] + dp[k][k]){ dp[i][j] = dp[i][k - ] * dp[k + ][j] + dp[k][k]; root[i][j] = k; } //dp2: ;i--) ; j <= n;j++) for (int k = i; k <= j;k++) ] * dp[k + ][j] + dp[k][k]){ dp[i][j] = dp[i][k - ] * dp[k + ][j] + dp[k][k]; root[i][j] = k; } printf(][n]); dfs(, n); sys; ; }
WAY2:
#include<bits/stdc++.h> #define int long long #define sys system("pause") using namespace std; #define N 32 int dp[N][N], ans,n,root[N][N],v[N]; int sear(int l,int r){ if(dp[l][r]) return dp[l][r]; if(l==r) return v[l]; if (r < l) ; for (int k = l; k <= r;k++){ ) * sear(k + , r) + dp[k][k]; if(t>dp[l][r]){ dp[l][r] = t; root[l][r] = k; } } return dp[l][r]; } void dfs(int l,int r){//前序输出 if(l>r) return; if(l==r){ printf("%lld ",l); return; } printf("%lld ", root[l][r]);//根 dfs(l, root[l][r]-);//左,由图可知,找左子树只需要root[l][r]-1 dfs(root[l][r] + , r);//右 } void init(){ scanf("%lld", &n); ; i <= n;i++){ scanf("%lld", &v[i]); dp[i][i] = v[i]; } } int32_t main(){ init(); printf(,n)); dfs(, n); sys; ; }
WAY1要注意的地方:
原则:从小区间--->大区间
从i=n开始和从i=1开始其实都可以操作,那么 为什么我要单独拿出来说呢...
因为我把k=i,k=j那个顺序弄反了
以后要注意!
WAY2:记忆化搜索~树形--->无环
最大子段和
从cube 到square 到O(n)
这是人类思维闪闪发光的时刻!!!!!!!
给一个 数列 negative positive zero都有
求一段连续的,最大子段和
,sum=; ;i<n;i++) { sum=max(array[i],sum+array[i]); best=max(sun,best); } cout<<best<<endl;
多么!clever!多么!wise!
最长上升子序列/最长不上升子序列
LUOGUP1020导弹拦截
问题1:求最长不上升子序列
问题2:求最长上升子序列-----因为导弹系统只能拦截下降的序列,那么求最长上升子序列,就可以得到导弹系统的数量
不信你算一算~
O(n*n)解法:
#include<bits/stdc++.h> using namespace std; #define int long long #define SYS system("pause"); ; int a[N],n,dp[N],ans1,ans2; int32_t main(){ while(scanf("%lld",&a[++n])!=EOF); n--; ;i--){//以i开头 dp[i] = ; ; j <= n;j++) if (a[j] <= a[i])//大于等于均可 dp[i] = max(dp[i], dp[j] + ); ans1 = max(dp[i], ans1); } ; i <= n;i++){//以i结尾 dp[i] = ; ; j < i;j++) if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + ); ans2 = max(dp[i], ans2); } cout << ans1<<endl<<ans2; SYS ; }
HDU1257
导弹拦截:
每一个导弹拦截系统可以拦截一串不严格下降的序列
那么,要求最少拦截系统数目,上升子序列的最大长度,就是需要安装的导弹系统的数目
初始化:dp[]=1;
状态:if(a[i]<a[j]&&i<=j) dp[j]=max(dp[i]+1,dp[j])
#include<bits/stdc++.h> using namespace std; #define int long long #define SYS system("pause"); ; int a[N],n,dp[N]; int32_t main(){ while(cin >> n){ ; i <= n;i++) cin >> a[i]; ; ; i <= n;i++){ dp[i] = ; ; j <= i;j++) if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + ); ans = max(dp[i], ans); } cout << ans<<endl; } SYS ; }
DP一下,马上出发的更多相关文章
- LightOJ1021 Painful Bases(状压DP)
容易想到状态dp[n][S][m](S是数字出现的集合),表示前n位用了数字集S且模k余数是m的方案数. 利用 (xy)base % k = ( x*base+y ) % k = (( x%k ) * ...
- hihocoder 1828 Saving Tang Monk II (DP+BFS)
题目链接 Problem Description <Journey to the West>(also <Monkey>) is one of the Four Great C ...
- 9.23 noip模拟试题
Problem 1 抓牛(catchcow.cpp/c/pas) [题目描述] 农夫约翰被通知,他的一只奶牛逃逸了!所以他决定,马上出发,尽快把那只奶牛抓回来. 他们都站在数轴上.约翰在N(O≤N ...
- 聪聪和可可[NOI2005]
[问题描述] 在一个魔法森林里,住着一只聪明的小猫聪聪和一只可爱的小老鼠可可.虽然灰姑娘非常喜欢她们俩,但是,聪聪终究是一只猫,而可可终究是一只老鼠,同样不变的是,聪聪成天想着要吃掉可可. 一天,聪聪 ...
- NOIP2014-6-14模拟赛
Problem 1 抓牛(catchcow.cpp/c/pas) [题目描述] 农夫约翰被通知,他的一只奶牛逃逸了!所以他决定,马上出发,尽快把那只奶牛抓回来. 他们都站在数轴上.约翰在N(O≤N≤1 ...
- NOIP练习赛题目1
有些题目可能没做,如计算几何.恶心模拟. 高级打字机 难度级别:C: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 早苗入手了最新的高级打字机 ...
- 2014-5-16 NOIP模拟赛
Problem 1 抓牛(catchcow.cpp/c/pas) [题目描述] 农夫约翰被通知,他的一只奶牛逃逸了!所以他决定,马上出发,尽快把那只奶牛抓回来. 他们都站在数轴上.约翰在N(O≤N≤1 ...
- NOIP模拟 7.03
Problem 1 抓牛(catchcow.cpp/c/pas) [题目描述] 农夫约翰被通知,他的一只奶牛逃逸了!所以他决定,马上出发,尽快把那只奶牛抓回来. 他们都站在数轴上.约翰在N(O≤N≤1 ...
- Javascript Promise 学习 (中)
时隔多日,对promise有了多一点点的了解. 最近用angularjs 做开发,所以研究了一下它的 $q 功能不算很强大,算是简化版的 Q.js 参考了一下源码,不过我的等级还差很多... 作为学习 ...
随机推荐
- JMeter压测基础(三)——Mysql数据库
JMeter压测基础(三)——Mysql数据库 环境准备 mysql驱动 JMeter jdbc配置 JMeter jdbc请求 1.下载mysql驱动:mysql-connector-java.ja ...
- C#文件增删改查
新建: private void btnnewfile_Click(object sender, EventArgs e) { //创建文件 string fileName = @"C:\T ...
- 【git】强制覆盖本地代码(与git远程仓库保持一致)
git强制覆盖: git fetch --all git reset --hard origin/master git pull git强制覆盖本地命令(单条执行): git ...
- Github上36893颗星!这个被称为下一代企业级应用首选技术你学了么?
用一句话概括:这个技术,是JAVA后端框架的龙头老大,执牛耳者.这个技术就是: Spring Boot春靴. Spring Boot到底凭什么成为Java社区最具影响力的项目?说直白点,他爹Spr ...
- 10个Python基础练习项目,你可能不会想到练手教程还这么有趣
美国20世纪最重要的实用主义哲学家约翰·杜威提出一个学习方法,叫做:Learning By Doing,在实践中精进.胡适.陶行知.张伯苓.蒋梦麟等都曾是他的学生,杜威的哲学也影响了蔡元培.晏阳初等人 ...
- MFC关于.rc文件 .rc2文件
.rc文件和.rc2文件 c和rc2都是资源文件,包含了应用程序中用到的所有的资源. 两者不同在于:rc文件中的资源可以直接在VC集成环境中以可视化的方法进行编辑和修改; 而rc2中的资源不能在VC的 ...
- 发布到FaceBook试玩广告,FaceBook要求要一个Html文件
Facebook 试玩广告具体要求: 试玩广告参数是创建试玩广告素材时要满足的要求. 试玩素材应为 HTML5 格式. 试玩广告素材不应使用 mraid.js 格式. 包含所有素材的试玩广告的单个 H ...
- matlab多个曲面如何画在一个坐标系中的疑问
matlab多个曲面如何画在一个坐标系中的疑问 [复制链接] [X,Y]=meshgrid(-3:0.1:3);Z=X.^2+Y.^2;mesh(X,Y,-Z)hold onmesh(X,Y,Z)
- Oracle游标介绍
Oracle游标使用详解: 游标: 用来查询数据库,获取记录集合(结果集)的指针,我们所说的游标通常是指显式游标,因此从现在起没有特别指明的情况,我们所说的游标都是指显式游标.要在程序中使用游标,必须 ...
- Docker 共有 13 个管理命令和 41 个通用命令,以下是常用 Docker 命令列表
开发人员一直在努力提高 Docker 的使用率和性能,命令也在不停变化.Docker 命令经常被弃用,或被替换为更新且更有效的命令,本文总结了近年来资深专家最常用的命令列表并给出部分使用方法. 目前, ...