UVA12511 Virus

一个O(N2)(N^2)的 LCIS DP

f [i] [j] 为 A的前i个元素和B的前J个元素的LCIS。

O(N3)(N^3) 做法:

此时的代码

1
2
3
4
5
6
7
8
9
10
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
if(a[i]==b[j])
{
for(k=0;k<j;k++)
if(a[i]>b[k]&&f[i][j]<f[i-1][k]+1) f[i][j]=f[i-1][k]+1;
}
else f[i][j]=f[i-1][j];
}

优化思路

发现f[i1][k]f [i-1] [k] 被 重复访问了多次。

因此我们的思路是让k=[1jk=[1,j),每个k只访问一次

因此我们可以找到之前最大的f[i1][k]f[i-1] [k]

令它为mxmx

接下来只需要判断A[i]是否比B[j]大,如果大了,就尝试更新 mx=max(mx,f[i1][j])mx=max(mx,f[i-1] [j])

如果$A[i]==B[j] 那么那么 f[i] [j]=max(f [i] [j] ,mx+1)$

因为f[i][j]f [i] [j]是由 $f[i-1] [k] 和 f[i-1] [j] 得到的,但是得到的,但是k已经被压缩成了每个j循环只做一次,因此时间复杂度变成了已经被压缩成了每个j循环只做一次,因此时间复杂度变成了O(N^2)$

因此我们得到了以下代码

1
2
3
4
5
6
7
8
9
10
11
for(i=1;i<=n;i++)
{
int mx=0;
for(j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j]) f[i][j]=max(f[i][j],mx+1);
else if(a[i]>b[j]) mx=max(mx,f[i-1][j]);
ans=max(ans,f[i][j]);
}
}

记住$ mx $每次都要 清空

完整代码如下

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
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#include<algorithm>

using namespace std;

int f[1100][1100];
int n,m;
int a[1100],b[1100];
int t,ans;

int main()
{
ios::sync_with_stdio(false);
int i,j;
cin>>t;
for(t;t>=1;t--)
{
memset(f,0,sizeof(f)); //每次记得清空数组
ans=0;
cin>>n;
for(i=1;i<=n;i++) cin>>a[i];
cin>>m;
for(i=1;i<=m;i++) cin>>b[i];
for(i=1;i<=n;i++)
{
int mx=0;
for(j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j]) f[i][j]=max(f[i][j],mx+1);
else if(a[i]>b[j]) mx=max(mx,f[i-1][j]);
ans=max(ans,f[i][j]); //其实ans 只需要比较 f[n] [1-> m]即可
}
}
cout<<ans<<endl;
}
return 0;
}

P1006 传纸条

一个n^4 或者 n^3 的 dp

已知两个状态 可以从左边或者右边传值给自己

f [i] [j] [k] [l] 表示 第一条路 走到 (i,j) 第二条路 走到 (k,l) 的最大好感度

a[i] [j] 表示 (i,j) 这个人的好感度

因为终点给的值是0,所以末态 是 f [n] [m-1] [n-1] [m] 的值

1
2
3
4
5
6
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];

貌似还有个n^3的优化,因此每一行每一列的i+j都不一样(参考八皇后问题)

悬线法入门

O(NM) 做法

P1387 最大正方形

1.设置 l[i] [j] 表示 (i,j)最左边能拓展到的点。

r[i] [j] 表示 (i,j)最右边能拓展到的点。

up[i] [j] 表示能拓展到的最上面的点。

初始化 l[i] [j] = r[i] [j] = j;

mp[i] [j] 表示地图 (i,j) 的状态 。

up[i] [j] 表示 (i,j) 能向上拓展的最大的高度,把它初始化为1。

然后我们可以轻易的推出预处理公式(我们暂时认为 我们要找最大全是1的正方形)

为了简化问题,我们先来看看矩形

如果左边那个点是1,那么这个点就可以向左边拓展,能拓展的最左边的点=上个点能拓展的最左边的点。

同理,右边也是一样。

1
2
3
4
5
6
7
8
9
10
11
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]&&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]&&mp[i][j]==1) r[i][j]=r[i][j+1];
return ;
}

现在预处理完毕后,我们枚举每个点 (i,j) 能够轻易得到这个点所在的最大矩形宽度a =(r [i] [j] -l [i] [j] +1)

于是横着的问题就解决了,竖着就是 up [i] [j]

因此它的最大面积就是 a*up[i] [j]

那么怎么转移呢?

如果上一个点是 1,这个点也是1的话,那么我们可以得到这个方程

l[i] [j]=max(l[i] [j],l[i-1] [j]), r[i] [j]=min(r[i] [j],r[i-1] [j]),up[i] [j]=up[i-1] [j]+1

别忘了这是个二维矩阵,i可以理解为高度。

于是代码就有了

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<stack>
#include<algorithm>

using namespace std;

int n,m,up[110][110],l[110][110],r[110][110],ans;
int mp[110][110];

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]&&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]&&mp[i][j]==1) r[i][j]=r[i][j+1];
return ;
}

inline void dp()
{
register int i,j;
for(i=2;i<=n;i++)
for(j=1;j<=m;j++)
{
if(mp[i][j]==mp[i-1][j]&&mp[i][j]==1) l[i][j]=max(l[i][j],l[i-1][j]),r[i][j]=min(r[i][j],r[i-1][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]);
ans=max(ans,b*b);
}
return ;
}

int main()
{
int i,j;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
scanf("%d",&mp[i][j]),l[i][j]=r[i][j]=j,up[i][j]=1;
getlr();
dp();
ans=sqrt(ans);
printf("%d",ans);
return 0;
}

O(S^2) 做法

S为点数量。

当 N M 极大的时候,上面的nm 做法就会TLE

因此我们有必要学习这个做法。

P1578 奶牛浴场

N,M<=30000

一看就知道不是NM的做法。

S^2的做法很怪,但是可以尽量看下去。

做法很生猛,对于每一个点都来和其他的点比较一下,然后找最大的。

首先排序了一次,让每个点的循环(i)只用对另外每一个点判断一次

设置 l,r,h; 这些东西他们都不是数组了

l,r,h意义和原来差不多。

首先按照高度进行排序

然后 for i,j in [1,s]

然后循环的时候j从i断开。

对于高度比第i小的点单独dp,比他大的单独dp

初始状态 r=m,l=0

对于j<i h=n-q[i].x

对于j>i h=q[i].x

然后比较的时候,r必然向左,l必然向右。

所以得到dp方程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for(i=1;i<=s;i++)
{
int r=m,l=0,h=n-q[i].x;
for(j=i+1;j<=s;j++)
{
if(h*(r-l)<=ans) break; //l变大,r变小必然会导致(r-l)变小,所以以后不用枚举了
ans=max(ans,(q[j].x-q[i].x)*(r-l));
if(q[i].y==q[j].y) break; //r=l=0
if(q[j].y>q[i].y) r=min(r,q[j].y);
if(q[j].y<q[i].y) l=max(l,q[j].y);
}
r=m,l=0,h=q[i].x;
for(j=i-1;j>=1;j--)
{
if(h*(r-l)<=ans) break;
ans=max(ans,(q[i].x-q[j].x)*(r-l));
if(q[i].y==q[j].y) break;
if(q[j].y>q[i].y) r=min(r,q[j].y);
if(q[j].y<q[i].y) l=max(l,q[j].y);
}
}

完整代码如下:

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 n,m,s;

struct que{
int x,y;
}q[10010];

int cmp1(que a,que b)
{
if(a.x==b.x) return a.y<b.y;
return a.x<b.x;
}

int cmp2(que a,que b)
{
if(a.y==b.y) return a.x<b.x;
return a.y<b.y;
}

int main()
{
int i,j;
scanf("%d %d",&n,&m);
scanf("%d",&s);
for(i=1;i<=s;i++)
{
scanf("%d %d",&q[i].x,&q[i].y);
}
q[++s].x=0;
q[++s].x=n,q[s].y=0;
q[++s].x=0,q[s].y=m;
q[++s].x=n,q[s].y=m;
int ans=0;
sort(q+1,q+s+1,cmp1);
for(i=1;i<=s;i++)
{
int r=m,l=0,h=n-q[i].x;
for(j=i+1;j<=s;j++)
{
if(h*(r-l)<=ans) break;
ans=max(ans,(q[j].x-q[i].x)*(r-l));
if(q[i].y==q[j].y) break;
if(q[j].y>q[i].y) r=min(r,q[j].y);
if(q[j].y<q[i].y) l=max(l,q[j].y);
}
r=m,l=0,h=q[i].x;
for(j=i-1;j>=1;j--)
{
if(h*(r-l)<=ans) break;
ans=max(ans,(q[i].x-q[j].x)*(r-l));
if(q[i].y==q[j].y) break;
if(q[j].y>q[i].y) r=min(r,q[j].y);
if(q[j].y<q[i].y) l=max(l,q[j].y);
}
}
sort(q+1,q+s+1,cmp2);
for(i=1;i<s;i++) ans=max(ans,(q[i+1].y-q[i].y)*n);
printf("%d",ans);
return 0;
}