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)\) 的算法。

posted @ 2022-04-15 19:48  Plozia  阅读(71)  评论(0编辑  收藏  举报