双进程:有两个决策相互影响且同时进行
二维平面:由一般线性的动态规划转移到二维平面之中,思考方式没有太大变化
精髓:拆解较大规模的子问题时,可以拆解成i规模更小,j规模更小,或者i和j规模都减小的子问题,即dp[i][j]通常与dp[i-1][j], dp[i][j-1], dp[i-1][j-1]有关。
题目1
最长公共子序列
题目描述
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。
令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列,使得对所有的j=0,1,…,k-1,有xij = yj。
例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
对给定的两个字符序列,求出他们最长的公共子序列。
输入格式
第1行为第1个字符序列,都是大写字母组成,以”.”结束。长度小于5000。
第2行为第2个字符序列,都是大写字母组成,以”.”结束,长度小于5000。
输出格式
输出上述两个最长公共自序列的长度。
样例数据
input
ABCBDAB.
BACBBD.
output
4
首先从公共可以得出这两个字符串都要包含这个子序列
所以我们状态设f[i][j]为x的前i位和y的前j为最长公共子序列的长度
易得(其实不易)
0 |
i=0,j=0 | |
f [i][j] |
max(f[i-1][j-1]+1,f[i][j]) |
i>0,j>0,xi==yj |
max(f[i][j-1],f[i-1][j]) |
i>0,j>0,xi==yj |
代码如下
#include<bits/stdc++.h>
using namespace std;
char ch1[5100],ch2[5100];
int len1,len2;
int f[5100][5100];
int main()
{
freopen("lcs.in","r",stdin);
freopen("lcs.out","w",stdout);
char ch;
len1=0;
cin>>ch;
while(ch!='.')
{
ch1[++len1]=ch;
cin>>ch;
}
cin>>ch;len2=0;
while(ch!='.')
{
ch2[++len2]=ch;
cin>>ch;
}
memset(f,0,sizeof(f));
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
f[i][j]=f[i][j-1];
if(f[i][j]<f[i-1][j])
{
f[i][j]=f[i-1][j];
}
if(ch1[i]==ch2[j]&&f[i][j]<=f[i-1][j-1])
{
f[i][j]=f[i-1][j-1]+1;
}
}
}
cout<<f[len1][len2];
return 0;
}
但是我发现了一个非常厉害的转换思想,源于洛谷
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
设字符串长度为n,m,那么想象我们有一个n+1行m+1列的网格图,只能从左下角往右、上两个方向走。定义每条路径的长度都为11。记第i行第j列为(i,j)。

求最长公共子序列本质上是在两个序列中寻找最多的配对,而且这些配对的位置在序列中的位置也要分别递增。
那么,如果x_ix与y_j相等,那么我们就从(i-1,j-1)向(i,j)连一条边。这在网格图中分明是一条条捷径,那么我们要寻找最长公共子序列,可不可以转化为寻找最短路,或者说寻找经过捷径次数最多的路径呢?

这个模型是很巧妙的,满足了配对的位置在序列中的位置分别递增(因为只能往右、上走)。
那么再看第二问。显然在这个模型中,不同的公共子序列对应的,不是至少有一条边不相同的路径,而是至少有一条捷径不相同的路径。那么这个该怎么DP呢?
设到达(i,j)(i,j)最多能经过的捷径数(即序列的两个前缀的最长公共子序列长度)为mf_{i,j},方案数为f_{i,j}。显然(i,j)可以从(i-1,j)和(i,j−1)转移,如果x_i=y_j那么还可以从(i−1,j−1)转移(mf加上11)。依次转移,如果新的mf更大则直接覆盖原信息,如果mf相等则f相加。
然而,再次注意不同路径的定义。那么是不是可能存在这样一种情况:到(i−1,j−1)的一条路径,分别转移给了(i-1,j)和(i,j−1),而再一次转移给了(i,j),没有经过不同的捷径,却计算了两遍!显然只有mf_{i-1,j-1}=mf_{i,j}的时候上述情况才会发生,那么这时我们从f_{i,j}减去f_{i-1,j-1}即可。
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
这个方法把抽象的序列实体化,并且用了类似于采花生的思路,很形象
题目2
最短编辑距离
题目描述
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
- 删除–将字符串 A 中的某个字符删除。
- 插入–在字符串 A 的某个位置插入某个字符。
- 替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
输入格式
第一行包含整数 n,表示字符串 A 的长度。
第二行包含一个长度为 n 的字符串 A。
第三行包含整数 m,表示字符串 B 的长度。
第四行包含一个长度为 m 的字符串 B。
字符串中均只包含大小写字母。
输出格式
输出一个整数,表示最少操作次数。
样例
输入样例1
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例1
4
如果第一题懂了,这题就是小easy
当前的状况用f[i][j]表示的话就是指1中第i个变成2中第j个的代价
删除 |
f[i][j]=min(f[i-1][j],f[i][j]) |
插入 |
f[i][j]=min(f[i][j-1],f[i][j]) |
替换 |
f[i][j]=f[i-1][j-1]+1 |
注意相等的时候 不一定是不操作,也可能要遵循上一步的操作(之前的插入或者替换)
#include<bits/stdc++.h>
using namespace std;
int a,b;
char s1[1111],s2[1111];
int f[1111][1111];
int main()
{
freopen("edit.in","r",stdin);
freopen("edit.out","w",stdout);
cin>>a;
for(int i=1;i<=a;i++)
{
cin>>s1[i];
}
cin>>b;
for(int i=1;i<=b;i++)
{
cin>>s2[i];
}
memset(f,10,sizeof(f));
for(int i=0;i<=a;i++)
{
f[i][0]=i;
}
for(int i=0;i<=b;i++)
{
f[0][i]=i;
}
for(int i=1;i<=a;i++)
{
for(int j=1;j<=b;j++)
{
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(s1[i]==s2[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
}
cout<<f[a][b];
return 0;
}
题目3
配置魔药
题目描述
在《Harry Potter and the Chamber of Secrets》中,Ron的魔杖因为坐他老爸的Flying Car撞到了打人柳,不幸被打断了,
从此之后,他的魔杖的魔力就大大减少,甚至没办法执行他施的魔咒,这为Ron带来了不少的烦恼。
这天上魔药课,Snape要他们每人配置一种魔药(不一定是一样的),Ron因为魔杖的问题,不能完成这个任务,他请Harry在魔药课上(自然是躲过了Snape的检查)帮他配置。
现在Harry面前有两个坩埚,有许多种药材要放进坩埚里,但坩埚的能力有限,无法同时配置所有的药材。
一个坩埚相同时间内只能加工一种药材,但是不一定每一种药材都要加进坩埚里。
加工每种药材都有必须在一个起始时间和结束时间内完成(起始时间所在的那一刻和结束时间所在的那一刻也算在完成时间内),每种药材都有一个加工后的药效。
现在要求的就是Harry可以得到最大的药效。
输入格式
输入文件的第一行有2个整数,一节魔药课的t(1≤t<≤500)和药材数n(1≤n≤100)。
输入文件第2行到n+1行中每行有3个数字,分别为加工第i种药材的起始时间t1、结束时间t2、(1≤t1≤t2≤t)和药效w(1≤w≤100)。
输出格式
只有一行,只输出一个正整数,即为最大药效。
样例数据
input
7 4
1 2 10
4 7 20
1 3 2
3 7 3
output
35
设第k种药的起始时间为a[k].st,结束时间为a[k].sw。
那么可得状态转移方程:
f[k][i][j]=max(f[k-1][a[k].st-1][j]); (i>=a[k].st)
f[k][i][j]=max(f[k-1][i][a[k].st-1]); (j>=a[k].st)
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int L,R,S;
}i[501];
int n,Time,ans,f[101][501][501];
bool Rule(Node t1,Node t2)
{
if (t1.L==t2.L)
return t1.R<t2.R;
return t1.L<t2.L;
}
int main() //双线程序列动态规划。
{
freopen("medic.in","r",stdin);
freopen("medic.out","w",stdout);
scanf("%d%d",&Time,&n);
for (int a=1;a<=n;a++)
scanf("%d%d%d",&i[a].L,&i[a].R,&i[a].S);
sort(i+1,i+n+1,Rule); //尽量按早的排序。
for (int a=1;a<=n;a++) //前多少个。
for (int b=0;b<a;b++) //坩埚1最后放的几号药品。
for (int c=0;c<a;c++) //坩埚2最后放的几号药品。
{
f[a][b][c]=f[a-1][b][c]; //因为排过序了,所以没有别的顾虑。
if (i[a].L>i[b].R) //不冲突,可以更新。
f[a][a][c]=max(f[a][a][c],f[a-1][b][c]+i[a].S);
if (i[a].L>i[c].R) //同理。
f[a][b][a]=max(f[a][b][a],f[a-1][b][c]+i[a].S);
ans=max(ans,max(f[a][b][a],f[a][a][c])); //顺便更新答案。
}
printf("%d",ans);
return 0;
}
但是
可以想背包一样倒着推,就可以省掉一维状态,非常秀
#include<bits/stdc++.h>
using namespace std;
int t,n,f[510][510];
struct nod
{
int s;
int e;
int w;
}y[110];
bool cmp(nod a,nod b)
{
return a.e<b.e;
}
int main()
{
freopen("medic.in","r",stdin);
freopen("medic.out","w",stdout);
cin>>t>>n;
for(int i=1;i<=n;i++)
cin>>y[i].s>>y[i].e>>y[i].w;
sort(y+1,y+n+1,cmp);
for(int i=1;i<=n;i++)
for(int j=t;j>=0;j--)
for(int k=t;k>=0;k--)
{
if(j>=y[i].e) f[j][k]=max(f[j][k],f[y[i].s-1][k]+y[i].w);
if(k>=y[i].e) f[j][k]=max(f[j][k],f[j][y[i].s-1]+y[i].w);
}
cout<<f[t][t];
return 0;
}
题目4
交错匹配
题目描述
有两行自然数,UP[1..N],DOWN[1..M],如果UP[I]=DOWN[J]=K,那么上行的第I个位置的数就可以跟下行的第J个位置的数连一条线,称为一条K匹配,但是同一个位置的数最多只能连一条线。
另外,每个K匹配都必须且至多跟一个L匹配相交且K≠L 。现在要求一个最大的匹配数。
例如:以下两行数的最大匹配数为8

输入格式
第一行有两个正整数N和M。第二行N个UP的自然数,第三行M个DOWN的自然数。其中0<N、M<=200,UP、DOWN的数都不超过32767。
输出格式
一个整数:最大匹配数
样例数据
input1
12 11
1 2 3 3 2 4 1 5 1 3 5 10
3 1 2 3 2 4 12 1 5 5 3
output1
8
input2
4 4
1 1 3 3
1 1 3 3
output2
0
这个题难就难在思路了
想到轮船问题了,那边是不允许相交,这里是必须且只能与一个匹配相交。
再仔细一想,两者什么关系都没有。(自己后来都想笑)
f[i][j]表示up取前i个数,down取前j个数时的最大匹配数,注意,不是i和j匹配。
仍然先写状态转移方程:
f[i][j]=max(f[i-1][j-1],f[i-1][j],f[i][j-1]);(这个还是比较好想的,但是不知道的条件太多了)
因为k!=l,即两个交叉的匹配的数字不能相同,故当up[i]==down[j]时不执行匹配。
假设存在kup,满足在doan的前j个数中,down[kup]==up[i]且kup距离j最近。
假设存在kdown,满足在up的前i个数中,up[kdown]==down[i]且kdown距离i最近。
则,当up[i]!=down[j]时: f[i][j]=max(f[i][j],f[kup-1][kdown-1]+2);
即up[i]与down[kup]匹配,down[j]与up[kdown]匹配。
所以我们要预处理求kup以及kdown。
#include<bits/stdc++.h>
using namespace std;
int up[222],down[222],Up[222][222],Down[222][222],f[222][222],m,n;
int main()
{
freopen("cross.in","r",stdin);
freopen("cross.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>up[i];
}
for(int j=1;j<=m;j++)
{
cin>>down[j];
}
for(int i=1;i<=n;i++) //down的前j个元素中,和up[i]相等的最靠近j下标
{
for(int j=2;j<=m;j++)
{
if(down[j-1]==up[i]) Up[i][j]=j-1;
else Up[i][j]=Up[i][j-1];
}
}
for(int i=1;i<=m;i++)//up的前j个元素中,与 down[i]相等的最靠近j下标的kdown
{
for(int j=2;j<=n;j++)
{
if(up[j-1]==down[i]) Down[j][i]=j-1;
else Down[j][i]=Down[j-1][i];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=max(max(f[i-1][j],f[i][j-1]),f[i-1][j-1]);
if(Down[i][j]!=0&&Up[i][j]!=0&&up[i]!=down[j])
{
f[i][j]=max(f[i][j],f[Down[i][j]-1][Up[i][j]-1]+2);
}
}
}
printf("%d",f[n][m]);
return 0;
}
题目5
马兰过河卒
题目描述
棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。
同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的。现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式
一行四个数据,分别表示B点坐标和马的坐标。
输出格式
一个数据,表示所有的路径条数。
样例数据
input
6 6 3 3
output
6
加法原理 左边的加上上边的
巧妙方法 1 地图中的马,终点都下移动一个,就不用处理边界
2 由于兵走位置比较特殊,所以

是成对角线往下延的,满足无后效性
状态转移方程
f[1][1] |
1 |
初始状态 |
f[i][j] |
0 |
vis[i][j]=1 |
f[i][j] |
f[i-1][j]+f[i][j-1] |
map[i][j]=0,i>0,j>0 |
f[i][0] |
f[i-1][0] |
map[i][j]=0,i>0,j>0 |
f[0][j] |
f[0][j-1] |
map[i][j]=0,i>0,j>0 |
#include<bits/stdc++.h>
using namespace std;
int m,n,x,y;
int vis[30][30];
long long f[30][30];
int dx[10]={0,-2,-1,1,2,2,1,-1,-2};
int dy[10]={0,1,2,2,1,-1,-2,-2,-1};
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
cin>>n>>m>>x>>y;
f[1][1]=1;
x++;
y++;
m++;
n++;
for(int i=0;i<=8;i++)
{
int xx=x+dx[i];
int yy=y+dy[i];
if(xx>0&&xx<=n&&yy>0&&xx<=m)
{
vis[xx][yy]=1;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(vis[i][j]!=1&&!(i==1&&j==1))
{
f[i][j]=f[i-1][j]+f[i][j-1];
}
}
}
cout<<f[n][m];
}
题目6
摘花生
略,之前打过
题目7
传纸条
Description
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。现在,请你帮助小渊和小轩找到这样的两条路径。
Input Format
输入的第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1<=m,n<=50)。
接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。
Output Format
输出共一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。
Sample Input
3 3
0 3 9
2 8 5
5 7 0
Sample Output
34
常见状态设置肯定是
f[i][j][k][l]表示第一个纸条在(i,j),第二个纸条在(k,l)
有点危险,所以引入一个步数p
f[p][i][j]表示当前走了p步,第一个人走到i行,第二个人走到j行的最大价值
那列就是p-j+1了,非常细减了一维
所以状态转移
f[p][i][j]=max记得f[1][1][1]=a[1][1] |
f[p-1][i][j] |
+a[i][p-i+1]+a[j][p-j+1] |
f[p-1][i-1][j] | ||
f[p-1][i][j-1] | ||
f[p-1][i-1][j-1] |
#include<bits/stdc++.h>
using namespace std;
int m,n,a[111][111],f[111][111][111];
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
cin>>m>>n;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
}
}
f[1][1][1]=a[1][1];
for(int p=2;p<=m+n-1;p++)
{
for(int i=1;i<=m&&i<=p;i++)
{
for(int j=1;j<=m&&j<=p;j++)
{
if(i==1&&j==1)
{
continue;
}
f[p][i][j]=max(max(f[p-1][i][j],f[p-1][i-1][j]),max(f[p-1][i][j-1],f[p-1][i-1][j-1]))+a[i][p-i+1]+a[j][p-j+1];
if(i==j) f[p][i][j]-=a[i][p-i+1];
}
}
}
cout<<f[n+m-1][m][m];
return 0;
}
题目8
矩阵切割
题目描述
给你一个矩阵,其边长均为整数。你想把矩阵切割成总数最少的正方形,其边长也为整数。切割工作由一台切割机器完成,它能沿平行于矩形任一边的方向,从一边开始一直切割到另一边。对得到的矩形再分别进行切割。
输入格式
输入文件中包含两个正整数,代表矩形的边长,每边长均在1—100之间。
输出格式
输出文件包含一行,显示出你的程序得到的最理想的正方形数目。
样例数据
input
5 6
output
5
样例解释

我起初的思路是找到以每个点为右下角最大正方形的边长,像这样

瞪眼看了半晌也没思路,看来还是我格局小了
这个题有一点点像区间dp的思路
可以设置f[i][k] + f[i][j - k]表示第一种切矩形方法的最少正方形
f[k][j] + f[i - k][j]表示第二种切矩形方法的最少正方形
这里面f[i][k1]+f[i][j-k1]表示将矩形横着切开以后分成的两块包含的最少的矩形,
同理可得f[k2][j]+f[i-k2][j]是竖着切开后两块的最少矩形
#include<bits/stdc++.h>
using namespace std;
int m,n,f[111][111];
int main()
{
freopen("cuts.in","r",stdin);
freopen("cuts.out","w",stdout);
cin>>n>>m;
memset(f,10,sizeof(f));
for(int i=1;i<=min(n,m);i++)
{
f[i][i]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=1;k<i;k++)
{
f[i][j]=min(f[i][j],f[i-k][j]+f[k][j]);
}
for(int k=1;k<j;k++)
{
f[i][j]=min(f[i][j],f[i][j-k]+f[i][k]);
}
}
}
cout<<f[n][m];
return 0;
}
题目9
农田个数
题目描述
你的老家在农村。过年时,你回老家去拜年。你家有一片N×M农田,将其看成一个N×M的方格矩阵,有些方格是一片水域。你的农村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总共包含了多少个不存在水域的正方形农田。
两个正方形农田不同必须至少包含下面的两个条件中的一条:
-
边长不相等
-
左上角的方格不是同一方格
输入格式
输入数据第一行为两个由空格分开的正整数N、M(1<=m< n <=1000)
第2行到第N+1行每行有M个数字(0或1),描述了这一片农田。0表示这个方格为水域,否则为农田(注意:数字之间没有空格,而且每行不会出现空格)
输出格式
满足条件的正方形农田个数。
样例数据
input
3 3
110
110
000
output
5
这个题就是用f[i][j]来表示用该点右下角为顶点的不同正方形的个数
| 1 【1】【1】 | 1【1】【2】 | 1【1】【3】 |
| 1【2】【1】 | 2【2】【2】 | 2【2】【3】 |
| 1【3】【1】 | 2【3】【2】 | 3【3】【3】 |
观察上表,得

每个格子都和左边,上边,左上有关,min再,这就简单了
状态转移:
f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
代码:
#include<bits/stdc++.h>
using namespace std;
int m,n,f[1111][1111],ans;
string mapp[1111];
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>mapp[i];
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
if(mapp[i][j]=='1')
{
f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
ans+=f[i][j];
}
}
}
cout<<ans;
return 0;
}
第10题
创意吃鱼
题目描述
可爱猫猫家里长方形大池子中有很多鱼,她开始思考:到底要以何种方法吃鱼呢(猫猫就是这么可爱,吃鱼也要想好吃法 ^_*)。
她发现,把大池子视为01矩阵(0表示对应位置无鱼,1表示对应位置有鱼)有助于决定吃鱼策略。
在代表池子的01矩阵中,有很多的正方形子矩阵,如果某个正方形子矩阵的某条对角线上都有鱼,且此正方形子矩阵的其他地方无鱼,
猫猫就可以从这个正方形子矩阵“对角线的一端”下口,只一吸,就能把对角线上的那一队鲜鱼吸入口中。
猫猫是个贪婪的家伙,所以她想一口吃掉尽量多的鱼。请你帮猫猫计算一下,她一口下去,最多可以吃掉多少条鱼?
输入格式
第一行有两个整数n和m(n,m≥1),描述池塘规模。接下来的n行,每行有m个数字(非“0”即“1”)。每两个数字之间用空格隔开。
输出格式
只有一个整数——猫猫一口下去可以吃掉的鱼的数量,占一行,行末有回车。
样例数据
input
4 6
0 1 0 1 0 0
0 0 1 0 1 0
1 1 0 0 0 1
0 1 1 0 1 0
output
3
非常复杂的一个题,但思路还是转化常规;
设f[i][j]表示以i为横坐标,j为纵坐标的点为右下角的满足条件的子矩阵的对角线长度,
那么我们可以用f[i-1][j-1]即该坐标的斜上方的子矩阵的对角线长度+1得到f[i][j]。
但这个推法存在一个问题,无法保证其余的元素全为0。
所以我们要另外开两个数组,
一个用来装该坐标点左(右)边连续0的长度,
一个用来装该坐标点上边连续0的长度,
然后我们取这两个长度和f[i-1][j-1]这三者的最小值来+1作为f[i][j]。才能得到正确的答案。
dp[i][j]min |
c[i-1][j] |
+1 |
r[i][j-1] | ||
dp[i-1][j-i] |
左,右,上中0的个数可以以预处理一下
min需要2次,因为对角线有2种(有点乱)

第一遍80分;
死因:没用scanf读1000*1000的表格
#include<bits/stdc++.h>
using namespace std;
int m,n,a[2600][2600],c[2600][2600],r[2600][2600],dp[2600][2600],ans;
int main()
{
freopen("meal.in","r",stdin);
freopen("meal.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(!a[i][j])
{
c[i][j]=c[i-1][j]+1;
r[i][j]=r[i][j-1]+1;
}
else
{
dp[i][j]=min(min(c[i-1][j],r[i][j-1]),dp[i-1][j-1])+1;
ans=max(ans,dp[i][j]);
}
}
}
memset(c,0,sizeof(c));
memset(r,0,sizeof(r));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
for(int j=m;j>=1;j--)
{
if(!a[i][j])
{
c[i][j]=c[i-1][j]+1;
r[i][j]=r[i][j+1]+1;
}
else
{
dp[i][j]=min(min(c[i-1][j],r[i][j+1]),dp[i-1][j+1])+1;
}
ans=max(ans,dp[i][j]);
}
}
cout<<ans<<endl;
return 0;
}
题目11
方格取数(弱鸡版)
题目描述
设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:

某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数N,表示 N×N 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从 1 开始。
一行“0 0 0”表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
样例
输入样例
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
输出样例
67
和传纸条一样,不解释
#include<bits/stdc++.h>
using namespace std;
int a,b,c,n,ss;
int m[20][20],f[40][20][20];
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
cin>>n;
for(int i=1;;i++)
{
cin>>a>>b>>c;
if(a!=0&&b!=0&&c!=0) m[a][b]=c;
else break;
}
f[1][1][1]=m[1][1];
for(int p=2;p<=2*n;p++)
{
for(int i=1;i<=n&&i<=p;i++)
{
for(int j=1;j<=n&&j<=p;j++)
{
if(i==1&&j==1) continue;
f[p][i][j]=max(max(f[p-1][i][j],f[p-1][i-1][j]),max(f[p-1][i][j-1],f[p-1][i-1][j-1]))+m[i][p-i+1]+m[j][p-j+1];
if(i==j) f[p][i][j]-=m[i][p-i+1];
}
}
}
cout<<f[n+n-1][n][n];
}
题目12
方格取数(大佬版)
【问题描述】
设有 n×m 的方格图,每个方格中都有一个整数。现有一只小熊,想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,并且不能重复经过已经走过的方格,也不能走出边界。小熊会取走所有经过的方格中的整数,求它能取到的整数之和的最大值。
【输入格式】
第一行有两个整数 n,m。
接下来 n 行,每行 m 个整数,依次代表每个方格中的整数。
【输出格式】
一个整数,表示小熊能取到的整数之和的最大值。
【数据规模与约定】
对于20%的数据,n,m≤5。
对于40%的数据,n , m ≤ 50 。
对于70%的数据,n , m ≤ 300。
对于100%的数据,1≤n,m≤10^3。方格中整数的绝对值不超过10^4。
毫无思路啊
因为感觉没有无后效性了,dp做不了,3维状态感觉还是不行,dfs25分。。
dfs:(感谢我头像这哥们)
#include<bits/stdc++.h>
using namespace std;
const int dx[4]={1,0,-1};
const int dy[4]={0,1,0};
int n,m,ans=-1e9,sum=0;
int a[1100][1100];
bool f[1100][1100];
void dfs(int x,int y)
{
f[x][y]=1;
if(x==n&&y==m)
{
ans=max(ans,sum);
f[n][m]=0;
return ;
}
for(int i=0;i<=2;i++)
{
int xx=x+dx[i],yy=y+dy[i];
// if(!(xx>0&&xx<=n&&y>0&&yy<=m)) continue;
if(xx>0&&xx<=n&&y>0&&yy<=m)
{
if(!f[xx][yy])
{
sum+=a[xx][yy];
dfs(xx,yy);
sum-=a[xx][yy];
}
}
}
f[x][y]=0;
}
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
}
void print()
{
printf("%d",ans);
}
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
init();
sum=a[1][1];
dfs(1,1);
print();
return 0;
正解
将数据矩阵顺时针旋转90度,
则每一步由只能向上、向下或向右走一格转变为
只能向左、向右或向下走一格,
加上题目给出的“不能重复经过已经走过的方格”的约束,
则走法便退化为了只能向下、向右走一格,
显然此时已经是经典的DP问题了,
满足DP“无后效性”的特点,便可按经典DP的自底向上方法或最后一步进行分析。
在代码实现上,为了达到旋转数据矩阵的要求,
在按题意读入数据后,按照先列后行的顺序处理便可。即以原矩阵的转置矩阵进行分析。
也就是m,n换一下
设f[i][j][0]表示由左方来到(i,j)的路径上的整数之和的最大值,
f[i][j][1]表示由右方来到(i,j)的路径上的整数之和的最大值:
● 由左方来到(i,j)的路径上的整数之和的最大值:f[i][j][0]=max(f[i][j][0],f[i][j−1][0]+a[i][j])
● 由右方来到(i,j)的路径上的整数之和的最大值:f[i][j][1]=max(f[i][j][1],f[i][j+1][1]+a[i][j])
● 由上方来到(i,j)的路径上的整数之和的最大值:
f[i][j][0]=max(f[i][j][0],max(f[i−1][j][0],f[i−1][j][1]))+a[i][j]
f[i][j][1]=max(f[i][j][1],max(f[i−1][j][0],f[i−1][j][1]))+a[i][j]
#include <bits/stdc++.h>
using namespace std;
int n,m;
long long f[1111][1111][2];
long long a[1111][1111];
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cin>>a[j][i];
}
memset(f,-10,sizeof(f));
f[1][1][0]=f[1][1][1]=a[1][1];
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(i>1)
{
f[i][j][0]=max(f[i][j][0],max(f[i-1][j][0],f[i-1][j][1])+a[i][j]);
f[i][j][1]=max(f[i][j][1],max(f[i-1][j][0],f[i-1][j][1])+a[i][j]);
}
if(j>1) f[i][j][0]=max(f[i][j][0],f[i][j-1][0]+a[i][j]);
}
for(int j=n;j>0;j--)
{
if(j<n) f[i][j][1]=max(f[i][j][1],f[i][j+1][1]+a[i][j]);
}
}
cout<<max(f[m][n][0],f[m][n][1]);
return 0;
}
题目13
滑雪
题目描述
顺治喜欢滑雪,这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待太监们来载你。顺治想知道载一个区域中最长的滑坡。
区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
顺治可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
输入格式
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 500)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
输出格式
输出最长区域的长度。
input
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
output
25
首先,我们可以发现此题有 3 个输入的要点。
一个点的高度 这个点的横坐标 这个点的纵坐标
既然是二维的DP,那么就要使用二维的图标分析
先看下面的一张图:

i 表示行数,j 表示列数,
在这里,可以很容易的就看出来最长的路线是一条螺旋的曲线向下滑。
那么,我们是怎么想到这么一条曲线的呢?
部分人就会很自然的想到,首先将高度排序,
然后从小到大选一条最大的路线。
这是不行的,如果怎么做的话,就没有考虑到坐标的影响,
这也就是题目的一个限制的条件,只能向相邻的点滑动,
而且只能由高处往低处滑动:
但是如果总是从最高点出发的话,那么这条路不一定是最大的。
把这道题转化一下,转化成其他的DP题。如果说把这二维的坐标全部转化为一维的数组
这个二维的图,如果说把它转化为一维的图的话,可以发现数组是这么排列的:
1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9
因为这数组原来全都是一维的,但是由于要将它改造成 5 * 5 的矩阵,
所以在上图中它执行了每五列换一次行。
根据最终的结果来看,它其实是这样的一个序列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
也就是一个有坐标限制的最长上升子序列、
那么,我们可以开一个结构数组来存储一个点的信息,
它的信息包括它的 x ,y 坐标和它的高度,
结构数组是一维的,但是它又可以同时存储多个信息,所以它是最佳的选择,
而且它还可以用 sort 进行排序。
所以,最终的结论就是,首先把整个地图接进来,
然后把每个点的信息存入一个结构数组里面,再对它们的高度进行排序,排序完之后再仿照求最长
上升子序列的方法来求最大的滑行长度。
状态转移死也就别不难了,只可能来自上下左右4个方向,比较一下就行
f[i][j]=max() |
f[i][j] |
f[i-1][j]+1 | |
f[i][j-1]+1 | |
f[i+1][j]+1 | |
f[i][j+1]+1 |
别忘了地图类的边界考虑
#include<bits/stdc++.h>
using namespace std;
struct node
{
int x,y,h;
}mapp[360000];
int n,m;
int len;
int ans=-1e9;
int a[666][666],f[666][666];
bool mysort(node x,node y)
{
return x.h <y.h ;
}
int main()
{
freopen("Shunzhi.in","r",stdin);
freopen("Shunzhi.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
mapp[++len].x=i;
mapp[len].y=j;
int ddd;
cin>>ddd;
a[i][j]=ddd;
mapp[len].h=ddd;
f[i][j]=1;
}
}
sort(mapp+1,mapp+1+m*n,mysort);
for(int i=1;i<=m*n;i++)
{
int xx=mapp[i].x ,yy=mapp[i].y ;
if(a[xx][yy]>a[xx-1][yy]&&xx-1>0)
{
f[xx][yy]=max(f[xx-1][yy]+1,f[xx][yy]);
}
if(a[xx][yy]>a[xx+1][yy]&&xx+1<=n)
{
f[xx][yy]=max(f[xx+1][yy]+1,f[xx][yy]);
}
if(a[xx][yy]>a[xx][yy-1]&&yy-1>0)
{
f[xx][yy]=max(f[xx][yy-1]+1,f[xx][yy]);
}
if(a[xx][yy]>a[xx][yy+1]&&yy+1<=m)
{
f[xx][yy]=max(f[xx][yy+1]+1,f[xx][yy]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
ans=max(ans,f[i][j]);
// cout<<f[i][j]<<' ';
}
// cout<<endl;
}
cout<<ans;
return 0;
}
本文探讨了如何使用动态规划解决涉及二维平面上的多种问题,如最长公共子序列、最短编辑距离、配置魔药等。文章通过具体题目案例,详细解析了如何将问题转化为二维动态规划的状态转移方程,并提供了相应的代码示例。动态规划方法通过抽象问题,将复杂问题简化为规模较小的子问题,从而有效解决问题。
1236

被折叠的 条评论
为什么被折叠?



