Codeforces 1749E Cactus Wall 题解 [ 紫 ] [ 01 BFS ] [ 图论建模 ] [ 构造 ] [ adhoc ]

一道很好的思维题,被教练碾压了。

观察

首先从题目给的样例入手:

5 5
.....
.....
.....
.....
.....

这种情况最终的答案是:

YES
....#
...#.
..#..
.#...
#....

所以我们大胆猜测,构造这样的仙人掌防线,需要我们斜着不间断地排布仙人掌。

这一点其实也可以从题目中 “仙人掌不能四联通放” “需要以四联通的形式堵住怪物” 观察出来,只是初看比较难想到。

因此,我们只要在图中构造一个斜向走的仙人掌防线即可。

假做法

既然我们要用最少的仙人掌去构造这样的一个放置方案,就需要利用到之前已经摆放过的仙人掌了。

同时看到我们求的是一个放置的轮廓线,并且是从左往右放的,那么我们就可以想到经典的按轮廓线转移的线性 dp 。

当这个位置是仙人掌时,我们不需要将这个位置再放上仙人掌,因此直接转移前面的:

\[dp[i][j]=\min(dp[i-1][j+1],dp[i-1][j-1]) \]

当这个位置是空的时,我们要在这里放上仙人掌,因此转移时要加一:

\[dp[i][j]=\min(dp[i-1][j+1],dp[i-1][j-1])+1 \]

当这个位置是与某个仙人掌四联通时,无法放置,因此不转移。

但是,这个做法实际上是假的,因为我们并不能保证仙人掌防线一定是一直从左往右的,例如:

.......#
......#.
.....#..
....#...
...#....
..#.....
.#......
..#.....
...#....
....#...
...#....
..#.....
.#......
#.......

可以从左上,左下,右上,右下四个地方转移,这就直接说明了本题不满足无后效性,不能用 dp 。因此需要换一种思路。

正解

根据上述分析我们知道,转移可以从左上,左下,右上,右下四个地方来。因此我们可以建出一个图,表示这个点能从其他什么点走过来,形成防线。

这样,就把 dp 变成了 无需满足无后效性的 图论问题。

这是经典的把有后效性的 dp 转化为最短路的 trick 。

复杂度更劣做法

我们把和仙人掌四联通的点设为不可以走的点,对于一个有向边 \(<u,v>\) ,如果 \(v\) 是已有仙人掌的点,那么把这条边的权设为 \(0\) ,否则设为 \(1\) ,这代表着走这里需不需要种仙人掌。

这里由于是无向图,所以要建两倍有向边。

接下来跑一遍 dijkstra 即可,求出的最短路径记录一下自己的先驱,然后顺着最短路径把这上面的点修改成仙人掌再输出就可以了。

时间为 \(O(\sum( nm \log (nm)))\),可以过但是没有到最优复杂度。

复杂度更优做法

观察到边权只有 \(0\)\(1\) ,因此我们可以使用 01 BFS 算法来在 \(O(n)\) 时间内求解最短路。

01 BFS 板子:Switch the lamp on

还要注意的是 01 BFS 只有在节点从 deque 里出来后才能判断是否抵达终点,因为如果在 push 时就判断,则会导致可能这个节点先更新了边权更大的节点,导致其先到终点就结束,而没有更新边权更小的节点导致算错最短路的情况。

我这里采用了数组模拟 deque ,为了减少常数,并且要注意这里数组没有办法直接开下,因此我们要用 vector 存。

坑点:vector 太大了,必须开在全局里,如果开在局部会爆栈导致 RE 。二维的 vector 在 clear 时要分维 clear ,无法一次 clear 掉全部。

时间为 \(O(\sum( nm ))\)

01 BFS 代码

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pi;
int n,m;
int gox[]={0,0,1,-1};
int goy[]={1,-1,0,0};
int xx[]={1,1,-1,-1};
int yy[]={-1,1,-1,1};
bool check(int x,int y)
{
	return (x>=1&&x<=n&&y>=1&&y<=m);
}
pi q[1500005];
vector<int>c[200005];//不能开在局部
vector<int>f[200005];
vector<pi>pre[200005];
vector<pi>cac;
int h,t;
void outp(int ex,int ey)
{
	cout<<"YES"<<endl;
	while((ex!=-1)&&(ey!=-1))
	{
		c[ex][ey]=0;
		pi tmp=pre[ex][ey];
		ex=tmp.first;
		ey=tmp.second;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(c[i][j]==0)cout<<'#';
			else cout<<'.';
		}
		cout<<endl;
	}
}
void solve()
{
	for(auto tmp:cac)
	{
		int x=tmp.first,y=tmp.second;
		c[x][y]=0;
		for(int i=0;i<4;i++)
		{
			int tx=x+gox[i],ty=y+goy[i];
			if(check(tx,ty)&&c[tx][ty]==1)c[tx][ty]=-1;
		}
	}
	h=750000,t=750000;
	for(int i=1;i<=n;i++)
	{
		if(c[i][1]!=-1)
		{
			if(c[i][1]==0)q[--h]={i,1};
			else q[t++]={i,1};
			f[i][1]=c[i][1];
			pre[i][1]={-1,-1};
		}
	}
	while(t-h>0)
	{
		pi now=q[h++];
		int x=now.first,y=now.second;
		int w=f[x][y];
		if(y>=m)//这里不能放在 push 时判断
		{
			outp(x,y);
			return;
		}		
		for(int i=0;i<4;i++)
		{
			int nx=xx[i]+x,ny=yy[i]+y;
			if(check(nx,ny)&&f[nx][ny]==0x3f3f3f3f&&c[nx][ny]!=-1)
			{
				int nw=w+c[nx][ny];
				f[nx][ny]=nw;
				if(t-h<=0||nw<=f[q[h].first][q[h].second])q[--h]={nx,ny};
				else q[t++]={nx,ny};
				pre[nx][ny]={x,y};
			}
		}
	}
	cout<<"NO"<<endl;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		for(int i=0;i<=n;i++)//多维 vector 要逐维 clear ,不然会 CE 。
		{
			c[i].clear();
			f[i].clear();
			pre[i].clear();
		}		
		cac.clear();
		cin>>n>>m;
		for(int i=1;i<=n;i++)
		{
			c[i].push_back(0);
			f[i].push_back(0x3f3f3f3f);
			pre[i].push_back({0,0});
			for(int j=1;j<=m;j++)
			{
				char tmp;
				cin>>tmp;
				if(tmp=='#')cac.push_back({i,j});
				c[i].push_back(1);
				f[i].push_back(0x3f3f3f3f);
				pre[i].push_back({0,0});
			}
		}	
		solve();
	}
	return 0;
}
posted @ 2024-07-15 00:59  KS_Fszha  阅读(36)  评论(0编辑  收藏  举报