P1514 [NOIP 2010 提高组] 引水入城 题解

题意:P1514 [NOIP 2010 提高组] 引水入城有点复杂,自己看吧。

思路

这里提供一个好像没见过的纯 DP 做法,不需要神秘的证明以及任何脑子,直接顺着思路做即可。

首先判断正确性就是从第一行的每一个点开始暴力搜索,看最后一行有没有点没被走到。最坏情况下第一行的每个点都会遍历以它自己为矩形左上角,地图右下角为右下角的整个矩形(类似于一个地图后缀)。因此搜索总复杂度为 \(O(mn^2)\)

在搜索的同时,我们发现我们可以记下从第一行的每个格子能够到达哪些最后一行的格子。这个东西是类比于建图的,我们用邻接矩阵存下来。我们这里设 \(bian_{i,j}=0/1\) 表示第一行的某个点 \(i\) 可以到达最后一行的某个点 \(j\)

然后就是 DP 了。我们设 \(f_{i,j}\) 表示在水能填满最后一行前 \(i\) 个格子的前提下,仅能使用第一行前 \(j\) 个格子建造蓄水厂的最小数量。

由于每一步都要保证最后一行前 \(i\) 个格子是被填满的,因此直接暴力枚举从最后一行什么地方转移即可,设转移的地方为 \(k\)。但是我么还要保证从 \(k+1\)\(i\) 的这些最后一行的格子一定会被填满,因此要么是 \(j-1\) 及其以前的第一行的点就已经可以填满前 \(i\) 个格子了,要么是当前这个点 \(j\) 可以填满 \(k+1\)\(i\) 之间的所有格子,从 \(f_{k,j-1}\) 转移过来。

具体而言,转移方程如下:

\[f_{i,j}=\min \left\{\begin{matrix} f_{i,j-1} &\\ f_{k,j-1}+1 &,k\in [0,i-1]\wedge [k+1,i]\text{都可以被} j\text{完全覆盖} \end{matrix}\right. \]

再次强调一下这里的 \(i,k\) 是最后一行上的点,\(j\) 是第一行上的点。
边界条件即为 \(f_{0,i}=0\),答案为 \(f_{m,m}\)。(不得不说 ccf 数据真的水,我刚开始只把 \(f_{0,0}\) 赋值为零得了 90 分,意味着只有一个测试点不在第一行第一个点上建蓄水厂)

然后 DP 枚举了 \(i,j,k\) 三个变量,复杂度 \(m^3\)。由于 \(n,m\) 同阶,因此总复杂度 \(n^3\) 大概可以过。

code

实现细节上在 \(f_{i,j}\)\(k\) 处转移时取了个巧。我们将 \(k\) 倒序枚举使得当 \(bian_{j,k+1}\) 不为 1 的时候直接 break 即可。(注意我们始终保持的都是 \([k+1,i] \text{都可以被} j \text{完全覆盖}\)

最后代码很短,好写好调。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=505;
int n,m,mp[N][N],sign[N],lx[5]={0,0,1,-1},ly[5]={1,-1,0,0},bian[N][N],f[N][N],dfn[N][N];
void dfs1(int u,int x,int y){
	if(dfn[x][y]) return ;dfn[x][y]=1;
	if(x==n) bian[u][y]=1,sign[y]=1;
	for(int i=0;i<4;i++) if(x+lx[i]>=1&&x+lx[i]<=n&&y+ly[i]>=1&&y+ly[i]<=m&&mp[x][y]>mp[x+lx[i]][y+ly[i]]) dfs1(u,x+lx[i],y+ly[i]);
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>mp[i][j];
	for(int i=1;i<=m;i++) memset(dfn,0,sizeof(dfn)),dfs1(i,1,i);
	int cnt=0;
	for(int i=1;i<=m;i++) if(!sign[i]){cnt++;}
	if(cnt){cout<<"0\n";cout<<cnt<<'\n';return 0;}
	memset(f,0x3f3f,sizeof(f));
	for(int i=0;i<=m;i++) f[0][i]=0;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++){
			for(int k=i-1;k>=0;k--) f[i][j]=min(f[i][j],f[i][j-1]);
			for(int k=i-1;k>=0;k--){
				if(!bian[j][k+1]) break;
				f[i][j]=min({f[i][j],f[k][j-1]+1});
			}
		}
	}
	cout<<"1\n"<<f[m][m]<<'\n';
	return 0;
}
posted @ 2025-03-26 20:29  all_for_god  阅读(31)  评论(0)    收藏  举报