【COCI】 2271 邮递员 暴力(不是树形DP)题解 c++

【COCI】邮递员 2271
题目描述
Mirko在一个山镇找到了一份邮递员的工作。这个镇可以看作一个N*N的矩形。每个区域可能是以下之一:房子K,邮政局P,草地 ‘.’。每个区域都有一个海拔。
每天早上,Mirko要送信给镇上所有的家庭。他从邮局P处开始,可以向8个方向到相邻的一个区域,当他送完最后一份信后,他必须回到邮局。
现在用Mirko走过的路线中海拔最高点和最低点之差来表示他的疲劳程度。帮他计算出送出所有的信最少的疲劳值。
输入
第一行包含整数N(2<=N<=50)。接下来的N行表示矩形。P只出现一次,而K最少出现一次。
接下来N行每行包含N个正整数,表示相应区域的海拔。均小于1000000.
输出
单独的一行,一个整数,表示最小的疲劳值。
样例输入
2
P.
.K
2 1
3 2
样例输出
0

分析: 每个点都有一个海拔,而要我们求符合要求的路线中,海拔值最高与最低的值的差最小。根据经验,大部分求最大-最小的题都是通过二分差值来AC的。于是马上得出一个复杂度略(很)高的解法:
1. 二分最大-最小
2. 枚举最小,算出最大
3. dfs||bfs, 验证算出的范围是否能让mirko完成任务(将范围外的点删去)
4. 输出答案
估算时间复杂度:
(log 1e6) //二分
* 1e6 //枚举下界
* 5050//dfs||bfs
有点大
优化一: 海拔值只有2500种,意味着我们只需要枚举下界2500次,即离散化。有多种方式,但为了简单并且方便,我推荐用STL的set||sort,高效又好写。
复杂度变为:20(log 1e6)
2500 *2500 = 125000000(明显超时)

优化二:由于房子你必须经过,所以枚举下界和上界时下界小于等于房子的最小值,上界大于等于房子的最大值;(最强力的剪枝,直接快了1s左右)

优化三:其实这个有点卡数据,dfs和bfs使用时枚举方向的数组顺序要找好,大家可以试试不同的顺序,也许就AC了呢,楼主就靠这个把时间从1000打到了985,好不容易AC(见代码)。

优化四:dfs时判断点能不能走,用vis,在函数外预处理,反正楼主靠此搞了300ms回来。

贴AC代码:985ms
#include<cstdio>
#include<cstring>
#include<set>
#include<queue>
#include<algorithm>
#define maxn 55
#define F first
#define S second
using namespace std;

set<int>s;
int n,dis[maxn][maxn],cnt_k;
char map[maxn][maxn];
pair<int,int>b,now,k[2505];
int U[8]={-1,-1,-1,0,0,1,1,1},z[8]={-1,0,1,-1,1,-1,0,1};

bool vis[maxn][maxn];

bool check(int u,int v){
	return u>0&&u<=n&&v>0&&v<=n&& !vis[u][v];
}

void get(int &res) 
{ 
	res=0;
    int flag=1;char c; 
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1; 
    while(c>='0'&&c<='9')res=res*10+c-48,c=getchar(); 
	res*=flag; 
} 


void dfs(int x,int y){
	int u,v;
	for(int i=0;i<8;i++)
		if(check(u=x+U[i],v=y+z[i]))
		{
			vis[u][v]=1;
			dfs(u,v);
		}
}

bool search(int head,int tail){
	memset(vis,-1,sizeof vis);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(dis[i][j]>=head&&dis[i][j]<=tail)
				vis[i][j]=0;
	dfs(b.F,b.S);
	for(int i=0;i<cnt_k;i++)
		if(vis[k[i].F][k[i].S]!=1)
			return 0;
	return 1;
}

int main(){
	int maxh,minh,_minh=2e9,_maxh=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",map[i]+1);
		for(int j=1;j<=n;j++){
			if(map[i][j]=='P')
				b=make_pair(i,j);
			if(map[i][j]=='K')
				k[cnt_k++]=make_pair(i,j);
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			get(dis[i][j]);
			s.insert(dis[i][j]);
		}
	
		
	_minh=min(_minh,dis[b.F][b.S]);
	_maxh=max(_maxh,dis[b.F][b.S]);
	
	for(int i=0;i<cnt_k;i++){
		_minh=min(_minh,dis[k[i].F][k[i].S]);
		_maxh=max(_maxh,dis[k[i].F][k[i].S]);
	}
	
	set<int>::iterator it=s.end();
	it--;
	maxh=*it;
	minh=*s.begin();
	int l=_maxh-_minh,r=*(it)-*(s.begin()),mid;
	while(l<r){
		bool flag=0;
		mid=(l+r)>>1;
		for(it=s.begin();it!=s.end();it++)
		{
			if(*it>_minh || *it+mid<_maxh) continue;
			if(search(*it,*it+mid))
			{
				flag=1;
				break;
			}
		}
		if(!flag) l=mid+1;
		else r=mid;
	}	
	printf("%d",l);
}

UPD:几年后又码一遍:

#include<bits/stdc++.h>
using namespace std;
 
int n,sx,sy;
char s[55][55];
int h[55][55],vis[55][55],tim,sb[2505];
int tx[2505],ty[2505],cnt;
int dir[8][2] = {{0,1},{1,0},{-1,0},{0,-1},{-1,1},{-1,-1},{1,-1},{1,1}};
 
void dfs(int x,int y,int l,int r){
    if(vis[x][y] == tim || h[x][y] < l || h[x][y] > r) return;
    vis[x][y] = tim;
    for(int i=0,u,v;i<8;i++)
        if((u=dir[i][0]+x) >= 0 && u<=n && (v=dir[i][1]+y) >= 0 && v <= n)
            dfs(u,v,l,r);
}
 
bool chk(int d,int u){
    ++tim;
    dfs(sx,sy,d,u);
    for(int i=0;i<cnt;i++)
        if(vis[tx[i]][ty[i]] != tim)
            return 0;
    return 1;
}
 
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%s",s[i]);
        for(int j=0;j<n;j++)
            if(s[i][j] == 'P')
                sx = i , sy = j;
            else if(s[i][j] == 'K')
                tx[cnt] = i , ty[cnt++] = j;
    }
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            scanf("%d",&h[i][j]),sb[++sb[0]]=h[i][j];
    sort(sb+1,sb+1+sb[0]);
    sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
    int ans = 0x3f3f3f3f;
    for(int i=1,j=0;i<=sb[0];i++)
        for(;j+1<=sb[0] && chk(sb[j+1],sb[i]);j++)
            ans = min(ans , sb[i] - sb[j+1]);
    printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值