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,m≤50
对于另外20% 的数据,
p
=
1
p=1
p=1
对于100%的数据,
n
,
m
≤
300
,
s
≤
10
,
p
≤
15
n,m\le300,s\le10,p\le15
n,m≤300,s≤10,p≤15
题解
先来考虑部分分的做法:
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
By−MrWu)
将上述两种算法结合在一起即可。
第一步:
设 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);
}