luogu P3831 [SHOI2012]回家的路
题面传送门
对于这道题,我们很容易想到分层图(大概是因为那道分层图优化成矩乘太出名了),则这道题只需要建两层就够了,一层表示横的,一层表示竖的。
首先思考两层图之间怎么连接,很明显,由本层的点向下一层的同一节点建一条双向的边权为\(1\)的边
其次思考同一层图中怎么建边,我们可以用两个\(vector\)来保存每一行和列所有的点的编号,这个点向所有的同一行和同一列的点建边,则建图完成。
最后,将起点与终点分别开一个点,向同一行和列的建边,然后就是跑\(SPFA\),只要正常松弛就可以了
代码实现:
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define abs(x) ((x)<0?-(x):(x))
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
long long n,m,sx,sy,tx,ty,s,t,x[100039],y[100039],d[200039],head,h[200039],now,cur,ans;
struct yyy{
long long to,w,z;
}tmp,f[2000039];
inline void add(long long x,long long y,long long z){
f[++head]=(yyy){y,z,h[x]};
h[x]=head;
}
vector<long long> k1[20039],k2[20039];
queue<long long> q;
int main(){
memset(h,-1,sizeof(h));
memset(d,0x3f,sizeof(d));
register int i,j;
scanf("%lld%lld",&n,&m);
for(i=1;i<=m;i++){
scanf("%lld%lld",&x[i],&y[i]);
for(j=0;j<k1[x[i]].size();j++) add(i,k1[x[i]][j],abs(y[i]-y[k1[x[i]][j]])*2),add(k1[x[i]][j],i,abs(y[i]-y[k1[x[i]][j]])*2);
for(j=0;j<k2[y[i]].size();j++) add(i+m,k2[y[i]][j]+m,abs(x[i]-x[k2[y[i]][j]])*2),add(k2[y[i]][j]+m,i+m,abs(x[i]-x[k2[y[i]][j]])*2);
k1[x[i]].push_back(i);
k2[y[i]].push_back(i);
}
for(i=1;i<=m;i++) add(i,i+m,1),add(i+m,i,1);
scanf("%lld%lld%lld%lld",&sx,&sy,&tx,&ty);
for(i=0;i<k1[sx].size();i++) add(0,k1[sx][i],abs(sy-y[k1[sx][i]])*2);
for(i=0;i<k2[sy].size();i++) add(0,k2[sy][i]+m,abs(sx-x[k2[sy][i]])*2);
for(i=0;i<k1[tx].size();i++) add(k1[tx][i],2*m+1,abs(ty-y[k1[tx][i]])*2);
for(i=0;i<k2[ty].size();i++) add(k2[ty][i]+m,2*m+1,abs(tx-x[k2[ty][i]])*2);
d[0]=0;
q.push(0);
while(!q.empty()){
now=q.front();
//printf("%lld %lld\n",now,d[now]);
q.pop();
cur=h[now];
while(cur!=-1){
tmp=f[cur];
if(d[tmp.to]>d[now]+tmp.w) d[tmp.to]=d[now]+tmp.w,q.push(tmp.to);
cur=tmp.z;
}
}
if(d[2*m+1]>100000000000) printf("-1");
else printf("%lld\n",d[2*m+1]);
}
不开\(long long\)见祖宗!!!