开头扯蛋
DP的骚操作太多了,和
线段树一样。
因此本文带你看一些dp好题。
正文部分
一.普通的 DP
LCS/LIS问题
LIS
原来的f[i] 表示的是$ [1,i] $的LIS
现在改一下,用$ f[i] $  表示 长度为 i 的LIS 最后结尾的最小值。
此时贪心,i从前向后枚举。
加上一个当前长度的变量k
A 为原来的数组
构造出以下代码
getplace是指找f中第一个大于等于a[i] 的位置
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | int a[],f[]; register int i,j;
 int k=1;
 f[1]=a[1];
 for(i=1;i<=n;i++)
 {
 if(f[k]<a[i]) f[++k]=a[i];
 else f[getplace(a[i])]=a[i];
 }
 
 | 
LCS
和LIS的处理很像,只需要加一个标记数组map
$map[a[i]] $指向 $a[i] $ 在 a的位置 (听起来很傻)
然后就可以用 map[b[i]]来表示LIS代码中的A
| 12
 3
 4
 5
 6
 7
 8
 
 | int a[],b[],f[],map[];register int i,j;
 for(i=1;i<=n;i++) map[a[i]]=i;
 int k=1;
 f[1]=map[b[1]];
 for(i=1;i<=n;i++)
 if(map[b[i]]>f[k]) f[++k]=map[b[i]];
 f[getplace(map[b[i]])]=map[b[i]];
 
 | 
LCIS
我的上一篇博客给的很清楚,这里不再赘述
DP学习记
注意事项
二分其实是不一样的,比如这里的getplace 求的大于等于X的f中的第一个位置,写成代码应该是这样
总之求小于等于的时候是返回r,大于等于的时候范围l,定值的时候返回mid(有时候题目会要求返回-1)
方格上的DP
N^4 做法
f[i][j][k][l] 表示第一个点走到(i,j)第二个点走到k,l的价值最大值。
显然可以得到一个dp方程
其中$a [i] [j] 表示原来方格上面的( i,j )$号位置的数字。
| 1
 | f[i][j][k][l]=max(max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]),max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]))+a[i][j]+a[k][l];
 | 
此外,为了防止枚举的时候出现重复情况,枚举(k,l) 的时候l 要从j+1的位置开始向右枚举
代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | #include<iostream>#include<cstring>
 #include<cmath>
 #include<cstdio>
 #include<queue>
 #include<stack>
 #include<vector>
 #include<map>
 #include<set>
 #include<algorithm>
 
 using namespace std;
 
 int n,m,f[51][51][51][51];
 int a[51][51];
 
 int main()
 {
 ios::sync_with_stdio(false);
 int i,j,k,l;
 cin>>n>>m;
 for(i=1;i<=n;i++)
 for(j=1;j<=m;j++) cin>>a[i][j];
 for(i=1;i<=n;i++)
 for(j=1;j<=m;j++)
 for(k=1;k<=n;k++)
 for(l=j+1;l<=m;l++)
 f[i][j][k][l]=max(max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]),max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]))+a[i][j]+a[k][l];
 cout<<f[n][m-1][n-1][m];
 return 0;
 }
 
 | 
N^3 做法
参考八皇后问题,这里不再给出,可以去看洛谷这道题的题解。
棋盘制作
悬线法好题,考自对悬线法的理解。
悬线法一般都求的是最全为0或1的矩形,可这里要求的是0101的矩形
其实改一下判断就好了。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 
 | #include<iostream>#include<cstdio>
 #include<cstring>
 #include<cmath>
 #include<queue>
 #include<map>
 #include<set>
 #include<vector>
 #include<stack>
 #include<algorithm>
 
 using namespace std;
 
 int up[1100][1100],l[1100][1100],r[1100][1100];
 int n,m,mp[1100][1100],ans1,ans2;
 
 inline void getlr()
 {
 register int i,j;
 for(i=1;i<=n;i++)
 for(j=2;j<=m;j++)
 {
 if(mp[i][j]!=mp[i][j-1]) l[i][j]=l[i][j-1];
 }
 for(i=1;i<=n;i++)
 for(j=m-1;j>=1;j--)
 {
 if(mp[i][j]!=mp[i][j+1]) r[i][j]=r[i][j+1];
 }
 return ;
 }
 
 inline void dp()
 {
 register int i,j;
 register int ans=0;
 for(i=2;i<=n;i++)
 for(j=1;j<=m;j++)
 {
 if(mp[i][j]!=mp[i-1][j])
 {
 l[i][j]=max(l[i-1][j],l[i][j]);
 r[i][j]=min(r[i-1][j],r[i][j]);
 up[i][j]=up[i-1][j]+1;
 }
 int a,b;
 a=(r[i][j]-l[i][j]+1);
 b=min(a,up[i][j]);
 ans1=max(ans1,(r[i][j]-l[i][j]+1)*up[i][j]);
 ans2=max(ans2,b*b);
 
 }
 return ;
 }
 
 int main()
 {
 int i,j;
 scanf("%d %d\n",&n,&m);
 char t;
 for(i=1;i<=n;i++)
 for(j=1;j<=m;j++)
 {
 scanf("%d",&mp[i][j]);
 l[i][j]=j;
 r[i][j]=j;
 up[i][j]=1;
 }
 getlr();
 dp();
 printf("%d\n%d",ans2,ans1);
 }
 
 | 
二.区间DP
带有边界的DP
关路灯
普通的dp: f[i][j][k]表示人关了 [i,j] $的灯后所处的位置
但是仔细一想,貌似 k只有在左右边界的时候才会有最优情况,而且可以通过其他地方转移而来。
不得不说想到这种解法的人真的巨,空间复杂度低了很多,时间复杂度也优化了一维
| 12
 3
 4
 5
 6
 7
 
 | for(l=2;l<=n;l++)for(i=1;i+l<=n+1;i++)
 {
 j=i+l-1;
 f[i][j][0]=min(f[i+1][j][1]+jl[i][j]*g[i+1][j],f[i+1][j][0]+jl[i+1][i]*g[i+1][j]);
 f[i][j][1]=min(f[i][j-1][1]+jl[j][j-1]*g[i][j-1],f[i][j-1][0]+jl[i][j]*g[i][j-1]);
 }
 
 | 
全部代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 
 | #include<iostream>
 #include<cstdio>
 #include<cmath>
 #include<cstring>
 #include<queue>
 #include<stack>
 #include<vector>
 #include<set>
 #include<map>
 #include<algorithm>
 
 using namespace std;
 
 int f[60][60][20],g[60][60],jl[60][60];
 int place[60];
 int cost[60];
 int n,st,mmn=0x3f3f3f3f,tot;
 
 int main()
 {
 int i,j,k,l;
 scanf("%d %d",&n,&st);
 memset(f,0x3f,sizeof(f));
 f[st][st][0]=0;
 f[st][st][1]=0;
 for(i=1;i<=n;i++)
 {
 scanf("%d %d",&place[i],&cost[i]);
 tot+=cost[i];
 }
 for(i=1;i<=n;i++)
 for(j=i;j<=n;j++)
 g[i][j]=g[i][j-1]+cost[j];
 for(i=1;i<=n;i++)
 for(j=i;j<=n;j++)
 {
 g[i][j]=tot-g[i][j];
 
 }
 for(i=1;i<=n;i++)
 for(j=1;j<=n;j++)
 jl[i][j]=abs(place[i]-place[j]);
 for(l=2;l<=n;l++)
 for(i=1;i+l<=n+1;i++)
 {
 j=i+l-1;
 f[i][j][0]=min(f[i+1][j][1]+jl[i][j]*g[i+1][j],f[i+1][j][0]+jl[i+1][i]*g[i+1][j]);
 f[i][j][1]=min(f[i][j-1][1]+jl[j][j-1]*g[i][j-1],f[i][j-1][0]+jl[i][j]*g[i][j-1]);
 }
 mmn=min(f[1][n][0],f[1][n][1]);
 printf("%d",mmn);
 }
 
 | 
这一道题虽然比较简单,但是这个思想很有用,让我们能够在以后的OI旅程中用这个思想AC更多的题。
奶牛零食
入门的状压DP
四.用数据结构优化的DP
线段树优化DP
单调队列优化多重背包
五.采用先进思想优化的DP
石子合并四边形不等式
DAG上DAGdp
tarjan 缩点后拓扑dp
类似于spfa的玄学DP
差分约束
乖乖优化DP

领先大陆10年信息技术。