开头扯蛋

DP的骚操作太多了,和

线段树一样。

因此本文带你看一些dp好题。

正文部分

一.普通的 DP

LCS/LIS问题

LIS

原来的f[i]f[i] 表示的是$ [1,i] $的LIS

现在改一下,用$ f[i] $ 表示 长度为 ii 的LIS 最后结尾的最小值。

此时贪心,i从前向后枚举。

加上一个当前长度的变量k

AA 为原来的数组

构造出以下代码

getplace是指找ff中第一个大于等于a[i]a[i] 的位置

1
2
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]]map[b[i]]来表示LIS代码中的AA

1
2
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 求的大于等于XXff中的第一个位置,写成代码应该是这样

总之求小于等于的时候是返回r,大于等于的时候范围l,定值的时候返回mid(有时候题目会要求返回-1)

方格上的DP

P1006 传纸条

N^4 做法

f[i][j][k][l]f [i] [j] [k] [l] 表示第一个点走到(i,j)( 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)( k,l ) 的时候l 要从j+1的位置开始向右枚举

代码

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
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的矩形

其实改一下判断就好了。

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
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);
//ans1求的是矩形,ans2求的是正方形
}
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]表示人关了f [i] [j] [k] 表示 人关了 [i,j] $的灯后所处的位置

但是仔细一想,貌似 k只有在左右边界的时候才会有最优情况,而且可以通过其他地方转移而来。

不得不说想到这种解法的人真的巨,空间复杂度低了很多,时间复杂度也优化了一维

1
2
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]);
}

全部代码

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
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年信息技术。