luogu1514 引水入城(宽搜+DP)

题目连接

#同类的题目还有这些#

综合应用的好题:代码复杂度也比较符合t2的要求

题目大意:

    1 n*m的地图,第一行是入口(水),第n行是出口(城市),每格都有个高度值,只能从高到低移动;

    2 求用最少的入口,把所有的出口都联通。

解题思路:%HW同学,讲解细致!

    1 通过宽搜,把每个入口能到达的出口找出来,记录入口的左右边界(l,r);

        1.1 思考难点:如果一个入口不能到达连贯的区间的出口,那么中间的出口就永远无法到达;

        1.2 如果地图只有一行(出口也是入口),要注意特判。

    2 宽搜的时候,用v数组记录出口的到达情况,全部宽搜结束后判断,如果有出口未被到达,游戏结束0。

    3 如果2能过了,就做DP,求用最小的区间实现完全覆盖。

上代码:ps很多细节处理,要认真阅读代码注解

#include<cstdio>
#include<cstring>
#include<cstdlib>

int n,m,tou,wei;
int ma[510][510],b[510][510];//地图 
struct nod{int v,l,r;}a[510];//入口
int v[510];//出口

struct nodb{int x,y;}l[250005];//队列 

int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};

void bfs(int sx,int sy)
{
	memset(b,0,sizeof(b));//封路用 
	if(n==1) //一行的地图,特判 
	{
		v[sy]=1;a[sy].l=a[sy].r=sy;
	}
	tou=1;wei=2;l[1].x=sx;l[1].y=sy;
	b[sx][sy]=1;
	while(tou<wei)
	{
		int x=l[tou].x;
		int y=l[tou].y;
		for(int i=0;i<4;i++)
		{
			int xx=x+dx[i];
			int yy=y+dy[i];
			if(ma[xx][yy]<ma[x][y]&&b[xx][yy]==0)
			{
				if(xx>=1&&xx<=n&&yy>=1&&yy<=m)
				{
					if(xx==1) a[yy].v=0;//入口被别的入口进入的话,就废除
					if(xx==n)
					{
						v[yy]=1;//到达出口
						if(a[sy].l>yy) a[sy].l=yy;//更新入口能到达的左右边界 
						if(a[sy].r<yy) a[sy].r=yy; 
					} 
					b[xx][yy]=1;//封路 
					l[wei].x=xx;//进队 
					l[wei].y=yy; wei++; 
				}
			}
		}tou++;
	}
} 

void so1()
{
	//初始化 
	for(int i=1;i<=m;i++)
	{
		a[i].v=1;a[i].l=m+1;a[i].r=0;//初始化入口 
		v[i]=0;//初始化出口 
	}
	//跑宽搜 
	for(int i=1;i<=m;i++)
	{
		if(a[i].v==1) bfs(1,i);
	}
} 

void so2() //特判:出口是否都能到达 
{
	int su=0;
	for(int i=1;i<=m;i++)
	{
		if(v[i]==0) su++;
	}
	if(su!=0) { printf("0\n%d",su);exit(0); }
}

int minn(int x,int y) { return x<y?x:y; }
int f[510][510];//从i-j需要多少个入口才能覆盖 
int d[510];//从 1-i,最少要多少个入口才能覆盖 

void ddp()
{
	//初始化 
	memset(f,63,sizeof(f));
	memset(d,63,sizeof(d));
	for(int k=1;k<=m;k++)
	{
		if(a[k].r==0) continue;//能到达出口的入口,才需要处理 
		
		for(int i=a[k].l;i<=a[k].r;i++)
		{
			for(int j=i;j<=a[k].r;j++)
			{
				if(i==1) d[j]=1;
				f[i][j]=1;
			}
		}	
	}
	//求区间最小
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<i;j++)
		{
			d[i]=minn(d[i],d[j]+f[j+1][i]);
		}
	} 
	
	printf("1\n%d",d[m]);	
} 

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&ma[i][j]);
		}
	}
	
	so1();//通过宽搜,将每个入口的左右边界求出来 
	
	so2();//特判,如果出口不完全到达,游戏结束 
	
	ddp();//dp求区间最小值 
	
	return 0;
}



猜你喜欢

转载自blog.csdn.net/liusu201601/article/details/79801394