DP专题-专项训练:悬线法 DP
作者:互联网
1. 前言
本篇博文是悬线法 DP 的算法总结与专题训练。
没有学过悬线法 DP?
传送门:DP专题-学习笔记:悬线法 DP
悬线法 DP 还是比较偏板子的,而且题目也很容易一眼看出,这个时候就看 板子背的熟不熟 对悬线法 DP 的掌握如何了。
而且这次的 3 道题里面后面两道题的悬线法 DP 做法在思维量上吊打别的做法!
2. 练习题
题单:
P2701 [USACO5.3]巨大的牛棚Big Barn
这道题就是 这道题 的双倍经验,单纯看悬线法 DP 掌握如何。
两种解法:
法一:设 \(f_{i,j}\) 为 \((1,1)\) 到 \((i,j)\) 的解,转移方程:\(f_{i,j} = \min\{f_{i-1,j},f_{i,j-1},f_{i-1,j-1}\}+1\)
法二:悬线法 DP。
代码:
/*
========= Plozia =========
Author:Plozia
Problem:P2701 [USACO5.3]巨大的牛棚Big Barn
Date:2021/3/13
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 1000 + 10;
int n, t, a[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN], Up[MAXN][MAXN], ans;
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return (fh == 1) ? sum : -sum;
}
int Max(int fir, int sec) {return (fir > sec) ? fir : sec;}
int Min(int fir, int sec) {return (fir < sec) ? fir : sec;}
int main()
{
n = read(), t = read();
for (; t; --t)
{
int x = read(), y = read();
a[x][y] = 1;
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (a[i][j] == 0) l[i][j] = r[i][j] = j, Up[i][j] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 2; j <= n; ++j)
if (!a[i][j] && !a[i][j - 1]) l[i][j] = l[i][j - 1];
for (int i = 1; i <= n; ++i)
for (int j = n - 1; j >= 1; --j)
if (!a[i][j] && !a[i][j + 1]) r[i][j] = r[i][j + 1];
for (int i = 2; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (!a[i][j] && !a[i - 1][j]) Up[i][j] = Up[i - 1][j] + 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
if ((i ^ 1) && !a[i][j] && !a[i - 1][j])
{
l[i][j] = Max(l[i][j], l[i - 1][j]);
r[i][j] = Min(r[i][j], r[i - 1][j]);
}
ans = Max(ans, Min(r[i][j] - l[i][j] + 1, Up[i][j]));
}
printf("%d\n", ans);
return 0;
}
P1169 [ZJOI2007]棋盘制作
这道题有两种解法:单调栈解法与悬线法 DP。
单调栈做法:
正方形部分直接模仿 T1 解决。
长方形部分:
首先考虑预处理出 \(g_{i,j}\) 表示 \((i,j)\) 往上扩展的最大长度,然后这道题就变成了 这道题。
更加详细的解释可以看一看我这篇博文:数据结构专题-学习笔记 + 专项训练:单调栈
代码(因为是很久以前写的所以码风跟现在的不一样):
#include<bits/stdc++.h>
const int MAXN=2e3+10;
int n,m,a[MAXN][MAXN];
int read()
{
int sum=0,fh=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') fh=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
return sum*fh;
}
namespace zfx
{
int f[MAXN][MAXN],ans=0;
int solve(int op)
{
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]==op) f[i][j]=std::min(std::min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ans=std::max(ans,f[i][j]);
return ans*ans;
}
}
namespace cfx
{
int g[MAXN][MAXN],p,sta[MAXN],wid[MAXN],ans=0;
int solve(int op)
{
// std::cout<<op<<"\n";
memset(g,0,sizeof(g));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]==op) g[i][j]=g[i-1][j]+1;
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++) std::cout<<g[i][j]<<" ";
// std::cout<<"\n";
// }
for(int i=1;i<=n;i++)
{
memset(sta,0,sizeof(sta));
memset(wid,0,sizeof(wid));
p=0;sta[++p]=0;wid[p]=1;
for(int j=1;j<=m+1;j++)
{
int len=0;
while(p!=0&&g[i][sta[p]]>g[i][j])
{
len+=wid[p];
ans=std::max(ans,g[i][sta[p]]*len);
p--;
}
sta[++p]=j;
wid[p]=len+1;
}
}
return ans;
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=read()^((i^j)&1);
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++) std::cout<<a[i][j]<<"\n";
// std::cout<<"\n";
// }
std::cout<<std::max(zfx::solve(1),zfx::solve(0))<<"\n";
std::cout<<std::max(cfx::solve(1),cfx::solve(0))<<"\n";
return 0;
}
悬线法 DP:
简单。简单。太简单了!
首先对地图做一下处理(可以看代码), 然后跑两遍悬线法 DP 就可以了呀!
注意两次悬线法 DP 的时候需要清空 \(l,r,up\) 以及中间需要去一次反,因为只含 0 和只含 1 都是可以的。
所以你可以发现思维量是真的低。
/*
========= Plozia =========
Author:Plozia
Problem:P1169 [ZJOI2007]棋盘制作
Date:2021/3/13
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 2000 + 10;
int n, m, a[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN], Up[MAXN][MAXN], ans1, ans2;
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return (fh == 1) ? sum : -sum;
}
int Max(int fir, int sec) {return (fir > sec) ? fir : sec;}
int Min(int fir, int sec) {return (fir < sec) ? fir : sec;}
void Reverse()
{
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
a[i][j] = !a[i][j];
}
void Getans()
{
memset(l, 0, sizeof(l));
memset(r, 0, sizeof(r));
memset(Up, 0, sizeof(Up));
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (a[i][j]) l[i][j] = r[i][j] = j, Up[i][j] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 2; j <= m; ++j)
if (a[i][j] && a[i][j - 1]) l[i][j] = l[i][j - 1];
for (int i = 1; i <= n; ++i)
for (int j = m - 1; j >= 1; --j)
if (a[i][j] && a[i][j + 1]) r[i][j] = r[i][j + 1];
for (int i = 2; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (a[i][j] && a[i - 1][j]) Up[i][j] = Up[i - 1][j] + 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
{
if ((i ^ 1) && a[i][j] && a[i - 1][j])
{
l[i][j] = Max(l[i][j], l[i - 1][j]);
r[i][j] = Min(r[i][j], r[i - 1][j]);
}
ans1 = Max(ans1, Min(r[i][j] - l[i][j] + 1, Up[i][j]) * Min(r[i][j] - l[i][j] + 1, Up[i][j]));
ans2 = Max(ans2, (r[i][j] - l[i][j] + 1) * Up[i][j]);
}
}
int main()
{
n = read(), m = read();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
a[i][j] = read();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if ((i ^ j) & 1) a[i][j] = !a[i][j];
Getans(); Reverse(); Getans();
printf("%d\n%d\n", ans1, ans2);
return 0;
}
P4147 玉蟾宫
同上,有两种做法:单调栈做法与悬线法 DP 做法。
单调栈做法就是上面的长方形,代码不给了。
悬线法 DP 的做法也是类似于上面的做法。
代码:
/*
========= Plozia =========
Author:Plozia
Problem:P4147 玉蟾宫
Date:2021/3/13
========= Plozia =========
*/
#include <bits/stdc++.h>
#define Max(a, b) (((a) > (b)) ? (a) : (b))
#define Min(a, b) (((a) < (b)) ? (a) : (b))
typedef long long LL;
const int MAXN = 1000 + 10;
int n, m, a[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN], Up[MAXN][MAXN], ans;
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return (fh == 1) ? sum : -sum;
}
int main()
{
n = read(), m = read();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
{
char ch = getchar();
while (ch == '\n' || ch == ' ' || ch == '\r') ch = getchar();
if (ch == 'F') a[i][j] = 1;
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (a[i][j]) l[i][j] = r[i][j] = j, Up[i][j] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 2; j <= m; ++j)
if (a[i][j] && a[i][j - 1]) l[i][j] = l[i][j - 1];
for (int i = 1; i <= n; ++i)
for (int j = m - 1; j >= 1; --j)
if (a[i][j] && a[i][j + 1]) r[i][j] = r[i][j + 1];
for (int i = 2; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (a[i][j] && a[i - 1][j]) Up[i][j] = Up[i - 1][j] + 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
{
if ((i ^ 1) && a[i][j] && a[i - 1][j])
{
l[i][j] = Max(l[i][j], l[i - 1][j]);
r[i][j] = Min(r[i][j], r[i - 1][j]);
}
ans = Max(ans, (r[i][j] - l[i][j] + 1) * Up[i][j]);
}
printf("%d\n", (ans << 1) + ans);
return 0;
}
3. 总结
悬线法 DP 还是偏简单的,码量小,思维量小,简单好写还高效。
但是不排除有某些出题人为了卡悬线法 DP 而特别将 \(n,m\) 设的很大而障碍点个数 \(S\) 设的很小,此时我们可以利用最大化思想实现一个 \(O(S^2)\) 的算法。
标签:ch,悬线法,int,read,专题,MAXN,DP 来源: https://www.cnblogs.com/Plozia/p/16150720.html