啦啦啦那么我现在来总结动归了!
下面这段是别人写的
1,什么是动态规划(DP)?
非常重要!,不要认为概念不重要,理解的深刻,你才知道 对于什么样的问题去考虑有没有动态规划的方法,以及如何去使用动态规划。
1)动态规划是运筹学中用于求 解决策过程中的最优化数学方法。 当然,我们在这里关注的是作为一种算法设计技术,作为一种使用 多阶段决策过程最优的通用方法。
它是应用数学中用于 解决某类最优化问题的重要工具。
2)如果问题是由 交叠的子问题所构成,我们就可以用动态规划技术来解决它,一般来说,这样的子问题出现在对给定问题求解的 递推关系中, 这个递推关系包含了相
同问题的更小子问题的解。动态规划法建议,与其对交叠子问题一次又一次的求解,不如把每个较小子问题只求解一次并把结果记录在表中(动态规划也是空间换时间
的),这样就可以从表中得到原始问题的解。
关键词:
它往往是解决最优化问题滴
问题可以表现为多阶段决策(去网上查查什么是多阶段决策!)
交叠子问题:什么是交叠子问题,最有子结构性质。
动态规划的思想是什么:记忆,空间换时间,不重复求解,由交叠子问题从较小问题解逐步决策,构造较大问题的解。
-------------------------------------------------------------------------------------------------------------------------------------------------
关于斐波拉切数列可以作为最简单的一个例子来解释动态规划的思想,在前面讲斐波拉切数列时说过了,不再叙述。
一般来说,一个经典的动态规划算法时自底向上的(从较小问题的解,由交叠性质,逐步决策处较大问题的解),它需要解出给定问题的所有较小子问题。动态规划的
一个变种是试图避免对不必要的子问题求解。如果采用自顶向下的递归来解,那么就避免了不必要子问题的求解(相对于动态规划表现出优势),然而递归又会导致对
同一个子问题多次求解(相对于动态规划表现出劣势),所以将递归和动态规划结合起来,就可以设计一种基于记忆功能的从顶向下的动态规划算法,在后面会讲。
我在这里做几道vj上的dp题吧。
表示这两天敲什么题都一百多行的要疯了。。做做dp题放松心情!! 下次就要总结搜索啦!
我欢脱地去敲了这道我在一年前, 也就是刚接触编程大概一个月的时候做的题——采药, 然后——WA了!!哈哈哈哈
这证明了两点: 1, 我真的该先去换换脑子了
2, 我果然连对01背包都理解的并不好。。。竟然还指望做出来其它dp的题!!!
回到这道裸的01背包,
0, 1, 顾名思义, 即选或者不选, 所以对于这一类题, 首先的想法就是搜索啊, 然后随便加一个记忆化也可以过这道题
就像这样:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int t, m, w[105], c[105], f[105][1005], ans;
void dfs(int x, int ww, int cc){
if(x == m + 1){
if(ww <= t)ans = max(ans, cc);
return;
}
if(f[x][ww] < cc)f[x][ww] = cc;
else return;
dfs(x + 1, ww, cc);
if(ww + w[x] <= t)
dfs(x + 1, ww + w[x], cc + c[x]);
}
int main()
{
scanf("%d%d", &t, &m); t ++;
for(int i = 1; i <= m; i ++)
scanf("%d%d", &w[i], &c[i]);
dfs(1, 1, 1);
printf("%d\n", ans - 1);
// system("pause");
return 0;
}
然后我们分析这个代码的流程, 它在x 这个变量上是单调的, 所以说可以用一个循环来表示了, 就可以改成这样:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int t, m, w[105], c[105], f[105][1005];
int main()
{
scanf("%d%d", &t, &m);
for(int i = 1; i <= m; i ++)
scanf("%d%d", &w[i], &c[i]);
for(int i = 1; i <= m; i ++)
for(int j = t; j >= 1; j --)
if(j < w[i])f[i][j] = f[i - 1][j];
else f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + c[i]);
printf("%d\n", f[m][t]);
// system("pause");
return 0;
}
然后也可以改成这样:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int t, m, w[105], c[105], f[1005];
int main()
{
scanf("%d%d", &t, &m);
for(int i = 1; i <= m; i ++)
scanf("%d%d", &w[i], &c[i]);
for(int i = 1; i <= m; i ++)
for(int j = t; j >= w[i]; j --)
f[j] = max(f[j], f[j - w[i]] + c[i]);
printf("%d\n", f[t]);
// system("pause");
return 0;
}
关于第二层循环的方向:
如果是二维的话就是向上或者向下都一样的! 因为当前层对上一层不会产生影响, 但是如果是一维的话 在01 背包中就要注意 从大向小 循环, 因为如果改变了更小的点就会对后继的大的点的更新造成影响, 而完全背包则就是由小到大循环的, 因为这时一个点 可以多次被选择。
vijos 1037 搭建双塔
这道题可以想到, 每一个当前的元素只有三种情况: 不使用, 放到高的里面, 放到低的里面, 然后要维护的是 高的和低的的差。
这个是cdq写的插头DP, 写的很清晰易懂!
http://wenku.baidu.com/link?url=Db9bNVazpW0h8X9q2DoCwARyCgXjzOXL_FR9PU83IJK23Am7lNuKm_LCfyE1uisZt3rO4sshbVUlZdmKamiN5zykE_SCNb1D8IlXSqcmjHC
一定要写出来 vijos1110 一道插头dp裸题。 呜呜呜 为什么我看到人家那么长的代码就觉得自己肯定写不出来了呢T T
vj 1323
比较基础的dp题, 很容易想到开一个四维数组然后就能过了,, 注意要特判 n < 10 的情况。。不然会WA一个点。
今天我好想喜欢敲很宽的代码。。。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int n, a[105][5], f[105][11][11][11], ans = 200;
int main()
{
memset(f, 0x7f, sizeof(f));
scanf("%d", &n);
for(int i = 1; i <= n; i ++){
char c[15]; scanf("%s", c);
for(int j = 1; j <= 3; j ++){
a[i][j] = a[i - 1][j];
if(c[0] - 'A' + 1 == j)a[i][j] ++;}
}
if(n <= 10){
int ss = 0;
if(a[n][1])ss ++;
if(a[n][2])ss ++;
if(a[n][3])ss ++;
printf("%d\n", ss); return 0;
}
f[10][a[10][1]][a[10][2]][a[10][3]] = 0;
for(int p = 10; p <= n; p ++)
for(int i = 0; i <= 10; i ++)
for(int j = 0; j <= 10; j ++)
for(int k = 0; k <= 10; k ++)
if(f[p][i][j][k] < 200){
int mm = min(n, p + i);
f[mm][a[mm][1] - a[p][1]][j + a[mm][2] - a[p][2]][k + a[mm][3] - a[p][3]] = min(f[mm][a[mm][1] - a[p][1]][j + a[mm][2] - a[p][2]][k + a[mm][3] - a[p][3]], f[p][i][j][k] + 1);
mm = min(n, p + j);
f[mm][i + a[mm][1] - a[p][1]][a[mm][2] - a[p][2]][k + a[mm][3] - a[p][3]] = min(f[mm][i + a[mm][1] - a[p][1]][a[mm][2] - a[p][2]][k + a[mm][3] - a[p][3]], f[p][i][j][k] + 1);
mm = min(n, p + k);
f[mm][i + a[mm][1] - a[p][1]][j + a[mm][2] - a[p][2]][a[mm][3] - a[p][3]] = min(f[mm][i + a[mm][1] - a[p][1]][j + a[mm][2] - a[p][2]][a[mm][3] - a[p][3]], f[p][i][j][k] + 1);
}
for(int i = 0; i <= 10; i ++)
for(int j = 0; j <= 10; j ++)
for(int k = 0; k <= 10; k ++)
if(f[n][i][j][k] <= 200){
if(i > 0)f[n][i][j][k] ++;
if(j > 0)f[n][i][j][k] ++;
if(k > 0)f[n][i][j][k] ++;
ans = min(ans, f[n][i][j][k]);
}
cout<<ans<<endl;
// system("pause");
return 0;
}
vj 1173 物流运输
其实还是比较显然的 dp 与最短路的结合。。 手滑把边开小了之后它竟然会是T了而不是re,,真不知道是为什么。。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#define MAXN 105
using namespace std;
int n, m, k, e, head[105], no[105][105], f[105], ee, buke[105], dis[105];
bool vis[105];
struct Edge{
int to, next, c;
}edge[20005];
void addedge(int s, int t, int c){
edge[++ ee].to = t;
edge[ee].c = c;
edge[ee].next = head[s];
head[s] = ee;
}
int ask(int s, int t){
memset(dis, 0x3f3f3f3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(buke, 0, sizeof(buke));
for(int i = 1; i <= m; i ++)
for(int j = s; j <= t; j ++)
if(no[i][j]){
buke[i] = 1; break;
}
queue<int>q;
dis[1] = 0; vis[1] = 1;
q.push(1);
while(!q.empty()){
int u = q.front(); q.pop(); vis[u] = 0;
if(buke[u])continue;
for(int i = head[u]; i != -1; i = edge[i].next){
if(buke[edge[i].to])continue;
if(dis[edge[i].to] > dis[u] + edge[i].c){
dis[edge[i].to] = dis[u] + edge[i].c;
if(!vis[edge[i].to]){
vis[edge[i].to] = 1;
q.push(edge[i].to);
}
}
}
}
if(dis[m] == 0x3f3f3f3f)return 0x3f3f3f3f;
return dis[m] * (t - s + 1);
}
int main()
{
scanf("%d%d%d%d", &n, &m, &k, &e);
memset(head, -1, sizeof(head));
memset(f, 0x3f3f3f3f, sizeof(f));
while(e --){
int s, t, v;
scanf("%d%d%d", &s, &t, &v);
addedge(s, t, v); addedge(t, s, v);
}int d; scanf("%d", &d);
while(d --){
int p, a, b; scanf("%d%d%d", &p, &a, &b);
for(int i = a; i <= b; i ++)no[p][i] = 1;
}
for(int i = 1; i <= n; i ++){
f[i] = ask(1, i);
for(int j = 1; j < i; j ++)
f[i] = min(f[i], f[j] + ask(j + 1, i) + k);
}
cout<<f[n]<<endl;
// system("pause");
return 0;
}
vj 1191 最大子矩阵
m <= 2 这是有多逗。。
所以就分开来讨论吧。
我的dp挺丑的, 就是考虑每一列的两个数的选法有四种: 选上面那个, 选下面那个, 都选, 分开来选。然后依次转移。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int n, m, k, a[105][5], f[105][105], p[105][105], up[105][105], down[105][105], both[105][105], fenboth[105][105];
int main()
{
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
scanf("%d", &a[i][j]);
if(m == 1){
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= n; j ++)
f[i][j] = p[i][j] = -(1<<30);
for(int i = 0; i <= n; i ++)
p[i][0] = 0;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= min(i, k); j ++){
f[i][j] = max(f[i - 1][j], p[i - 1][j - 1]) + a[i][1];
p[i][j] = max(p[i - 1][j], f[i][j]);
}
printf("%d\n", p[n][k]);
}
else{
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= n; j ++)
p[i][j] = up[i][j] = down[i][j] = both[i][j] = fenboth[i][j] = -(1<<30);
for(int i = 0; i <= n; i ++) p[i][0] = 0;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= min(i, k); j ++){
up[i][j] = max(max(fenboth[i - 1][j], up[i - 1][j]), p[i - 1][j - 1]) + a[i][1];
down[i][j] = max(max(fenboth[i - 1][j], down[i - 1][j]), p[i - 1][j - 1]) + a[i][2];
both[i][j] = max(both[i - 1][j], p[i - 1][j - 1]) + a[i][1] + a[i][2];
if(j > 1)fenboth[i][j] = max(max(fenboth[i - 1][j], max(up[i - 1][j - 1], down[i - 1][j - 1])), p[i - 1][j - 2]) + a[i][1] + a[i][2];
p[i][j] = max(max(max(max(up[i][j], down[i][j]), both[i][j]), fenboth[i][j]), p[i - 1][j]);
}
printf("%d\n", p[n][k]);
}
// system("pause");
return 0;
}
下面是dp的优化
1, 比较常见的矩阵的优化, 前几天写过: 以前写的矩阵乘
一会再写几道
2, 然后还有就是斜率优化什么的, 我还没有学过T T
【树形dp】