【NOIP模拟赛】迷路 expand 题解

Description

Marah要出去买菜,但不小心迷路了,它记得所有菜店的坐标,也知道它现在的坐标。请你帮帮她,找 到一条买完菜的路吧。它已经急得快哭了,它想要买完菜回家。因此它需要你找到一条最短的路买菜。
她穿上了最新研发的机甲,这个机甲的体格能够变化, 为了使自己尽量炫酷,她因此它希望在路径最短 的情况下使自己的体格最大,即在移动时离障碍尽可能远。因为你开着上帝视角,所以你知道小T所在 的地图。你能帮它找到一条路吗?
注意: 1、此处的离障碍最远是保证在任何时候、在保证路径最短的情况下离障碍最远。当然小T只需 要你保证 在每个位置的体格之和尽可能大。 2、不需要考虑小T回家的路。

Input Format

第一行两个数 ,表示小T所在的地图大小和小T的最大体格。 体格为 表示小T会占据 ( 2 i + 1 ) ∗ ( 2 i + 1 ) (2i+1)*(2i+1) (2i+1)(2i+1) 个格子。 接下来 行每行 个数字表示地图,其中‘1’表示障碍,'0’表示空地。 接下来一行三个数 x , y , p x,y,p x,y,p表示小T所在的坐标 ( x , y ) (x,y) (x,y)和菜店数量 p p p。 接下来 p p p 行,第 两个数表示菜店 i i i所在 的坐标。

Output Format

输出两个数,表示最短路长度以及每个位置的体格之和。

Constraints

对于 20%的数据,所有菜市位置和小T所在的位置在一条水平直线上。 对于另外 30%的数据, s = 0 s=0 s=0 ,其中 20%的数据还满足 n , m ≤ 50 n,m\le50 n,m50
对于另外20% 的数据, p = 1 p=1 p=1
对于100%的数据, n , m ≤ 300 , s ≤ 10 , p ≤ 15 n,m\le300,s\le10,p\le15 n,m300,s10,p15

题解

先来考虑部分分的做法:
1.当体格系数为0时,不用考虑小T会膨胀,转变成了旅行商问题,预处理出两两菜市场之间的距离,鉴于是网格图,p遍bfs就可以处理出。 用 f [ i ] [ j ] f[i][j] f[i][j]表示目前在i点,走过的菜市场状态为二进制数j点,状压DP经典。

2.当只有一个菜市场时,只需要考虑小T能否膨胀,预处理出每一个点可以膨胀的最大限度,在bfs中直接比较即可。

Full marks —( B y − M r W u By -Mr Wu ByMrWu
将上述两种算法结合在一起即可。

第一步

d i s [ n u m ] [ i ] [ j ] dis[num][i][j] dis[num][i][j]表示第 n u m num num个菜市场到点 ( i , j ) (i,j) (i,j)的最短距离, v a l [ n u m ] [ i ] [ j ] val[num][i][j] val[num][i][j]表示第num个菜市场到点 ( x , y ) (x,y) (x,y)的最大膨胀和【包括菜市场】

对于每一个菜市场,bfs的同时先以最短距离为第一关键字,最大膨胀和为第二关键字,即最短距离被更新的时候要放弃当前的最大膨胀和,变成更新它的状态的最大膨胀和,当一个点 x x x访问到另一个已经访问过的点 y y y,若到 y y y的最短距离也可以由到 x x x的距离+1来得到,那么说明 y y y x x x当前的最短路上,那么就可以用到 x x x的最大膨胀和来更新到 y y y的最大膨胀和。(双关键字bfs)

第二步

f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0]表示目前走过菜市场的状态为 j j j,目前在 i i i点的最短距离
f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1]表示目前走过菜市场的状态为 j j j,目前在 i i i点,在当前最短路下的最大可膨胀度。

此时状压DP更新也是双关键字,类似于bfs更新操作,先比较距离,距离相同在比较最大膨胀即可。
【注意】:更新最大膨胀度的时候需要减去重复计算的菜市场的贡献。

第三步

从最短距离的状态中选出最大膨胀度的状态。
边枚举边比较即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
inline int read()
{
	int s=0,f=1;
	char ch;
	for(;ch<'0' || ch>'9';ch=getchar())   if(ch=='-') f=1;
	for(;ch>='0' && ch<='9';ch=getchar()) s=(s<<1)+(s<<3)+ch-'0';
	return s*f;
}
struct node{int x,y;};
int n,m,s,a[305][305],sum[305][305],w[305][305],f[21][(1<<15)+100][2];
int dis[21][305][305],val[21][305][305],vis[305][305],w1,w2,p,ans1,ans2;
/*
  dis[i][j][k]表示第i个菜市场到点(x,y)的最短距离,val[i][j][k]表示第i个菜市场到点(x,y)最大可膨胀值
  f[i][j][0]表示目前走过菜市场的状态为j,目前在i点的最短距离
  f[i][j][1]表示目前走过菜市场的状态为j,目前在i点,在当前最短路下的最大可膨胀度 
*/ 
inline int ask(int x1,int y1,int x2,int y2)
{
	return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
}
void check(int x,int y)//计算出每一个点的可膨胀度 
{
	for(int k=s;k>=0;k--)
	{
		if(x-k<1||x+k>n||y-k<1||y+k>m) continue;
		if(ask(x-k,y-k,x+k,y+k)==0)
		  { w[x][y]=k; return;}
	}
	return;
}
void BFS(int num,int sx,int sy)
{
	queue<node> q;
	while(q.size()) q.pop();
	q.push(node{sx,sy});
	dis[num][sx][sy]=0; val[num][sx][sy]=w[sx][sy];
	memset(vis,0,sizeof(vis)); vis[sx][sy]=1;
	while(q.size())
	{
		node tmp=q.front();
		int x=tmp.x,y=tmp.y; q.pop();
		for(int i=0;i<4;i++)
		{
			int xx=x+dx[i];
			int yy=y+dy[i];
			if(xx<1||yy<1||xx>n||yy>m) continue;
			if(a[xx][yy]) continue;
			if(!vis[xx][yy])
			{
				vis[xx][yy]=1;
				dis[num][xx][yy]=dis[num][x][y]+1;
				val[num][xx][yy]=val[num][x][y]+w[xx][yy];
			    q.push(node{xx,yy});
			}
			else
			{
				if(dis[num][xx][yy]==dis[num][x][y]+1)//xx,yy已经被访问到,它在x,y的最短路上---可以被更新 
				  val[num][xx][yy]=max(val[num][xx][yy],val[num][x][y]+w[xx][yy]);
			}
		}
	}
}
int main()
{
	freopen("expand.in","r",stdin);
	freopen("expand.out","w",stdout);
	n=read(); m=read(); s=read();
	for(rint i=1;i<=n;i++)
	  for(rint j=1;j<=m;j++)
	    a[i][j]=read();
	for(rint i=1;i<=n;i++)
	  for(rint j=1;j<=m;j++)
	    sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
	for(rint i=1;i<=n;i++)
	  for(rint j=1;j<=m;j++)
	    { 
		  if(a[i][j]==1) {w[i][j]=-999;continue;}
	      check(i,j);
	    }
	for(rint i=1;i<=p;i++)
	  for(rint j=1;j<=n;j++)
	    for(rint z=1;z<=m;z++)
	      dis[i][j][z]=1e9,val[i][j][z]=-1e9;
	w1=read(),w2=read(),w1++,w2++;//起点 
	p=read();
	node b[21];
	for(rint i=1;i<=p;i++)
	  {
	  	b[i].x=read(); b[i].y=read();
	  	b[i].x++; b[i].y++;
	  	BFS(i,b[i].x,b[i].y);
	  }
	for(int i=0;i<(1<<p);i++)
	  for(int j=0;j<=p;j++)
	    f[j][i][0]=1e9,f[j][i][1]=-1e9;
	for(int i=1;i<=p;i++)//初值
	  f[i][1<<(i-1)][0]=dis[i][w1][w2],f[i][1<<(i-1)][1]=val[i][w1][w2];
	for(int i=1;i<(1<<p);i++)
	  for(int j=1;j<=p;j++)
	    if(i&(1<<(j-1)))//j是目前所在的菜市场
	    {
	    	for(int z=1;z<=p;z++)//前往下一个没去过菜市场
	    	  if(!(i&(1<<(z-1))))
	    	  {
	    	  	if(f[j][i][0]+dis[j][b[z].x][b[z].y]<f[z][i|(1<<(z-1))][0])//最短路被更新,最大膨胀度要改变
	    	  	  f[z][i|(1<<(z-1))][0]=f[j][i][0]+dis[j][b[z].x][b[z].y],f[z][i|(1<<(z-1))][1]=f[j][i][1]+val[j][b[z].x][b[z].y]-w[b[j].x][b[j].y];
	    	  	if(f[j][i][0]+dis[j][b[z].x][b[z].y]==f[z][i|(1<<(z-1))][0])//最短路相同,更新最大膨胀度                                                                                  
	    	  	  f[z][i|(1<<(z-1))][1]=max(f[z][i|(1<<(z-1))][1],f[j][i][1]+val[j][b[z].x][b[z].y]-w[b[j].x][b[j].y]);//减去重复计算的j菜市场的贡献
	    	  }
	    }
	ans1=1e9,ans2=-1e9;
	for(int i=1;i<=p;i++)
	  { 
	   if(f[i][(1<<p)-1][0]<ans1) ans2=f[i][(1<<p)-1][1],ans1=f[i][(1<<p)-1][0];
	   if(f[i][(1<<p)-1][0]==ans1) ans2=max(ans2,f[i][(1<<p)-1][1]);
      }
    printf("%d %d",ans1,ans2);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值