【NOIP校内模拟】“新”的家园(思维题 建图最短路)
好题
同样的 我们考虑把环展开成链来想
我们引入一个概念:关键点 即 环外边的起点终点
对于u,v两点 u,v的最短路,必定是 环外边+环上连续一段...这样的几个组合拼成的
其次我们发现 “环上连续一段” 是很费时间的 极大地拖慢了我们spfa的效率 那么我们似乎只需要对于相邻两个关键点连一条权值为原来环上距离的边就快的多了
那么建好图后 再仔细考虑如何处理询问u v
他们有可能不是关键点
如果不是关键点( 不妨设为u) 那我们就把原来u相邻的俩关键点连的边删掉,再连接u和这两个关键点 跑一遍spfa
一次查询完了过后 再删除痕迹
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 500005
#define M 205
#define Q 30005
using namespace std;
int n,E,q,tot,first[N],ew[N],sum[N];
int pt_cnt,pt[N];
bool iskey[N];
template<class T>
void read(T &x)
{
x=0;
static char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
struct Node
{
int to,next,val;
}edge[2*N+2*Q];
inline void addedge(int x,int y,int z)
{
tot++;
edge[tot].to=y;
edge[tot].next=first[x];
edge[tot].val=z;
first[x]=tot;
}
int sta[N],top;
inline void insert(int x) //插入环上非关键点
{
if(iskey[x]) return;
for(int i=2;i<=pt_cnt;i++)
{
if(pt[i-1]<=x&&x<=pt[i]) //夹♂在中间
{
addedge(x,pt[i-1],sum[x-1]-sum[pt[i-1]-1]); //找相邻关键点连边
addedge(pt[i-1],x,sum[x-1]-sum[pt[i-1]-1]);
addedge(x,pt[i],sum[pt[i]-1]-sum[x-1]);
addedge(pt[i],x,sum[pt[i]-1]-sum[x-1]);
sta[++top]=x; sta[++top]=pt[i-1]; sta[++top]=pt[i]; sta[++top]=x;
}
}
}
queue <int> que;
int dis[N];
bool inque[N];
inline int spfa(int s,int t)
{
if(s==t) return 0;
memset(inque,false,sizeof(inque));
for(int i=1;i<=pt_cnt;i++) dis[pt[i]]=INF; //这只是对关键点的spfa
que.push(s);
inque[s]=true; dis[s]=0; dis[t]=INF;
while(!que.empty())
{
int now=que.front();
que.pop(); inque[now]=false;
for(int u=first[now];u;u=edge[u].next)
{
int vis=edge[u].to;
if(dis[vis]>dis[now]+edge[u].val)
{
dis[vis]=dis[now]+edge[u].val;
if(!inque[vis])
{
inque[vis]=true;
que.push(vis);
}
}
}
}
return dis[t];
}
inline void del(int now)
{
first[now]=edge[first[now]].next;
}
inline void Recovery()
{
while(top) del(sta[top--]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
read(n); read(E); read(q);
memset(ew,0x3f,sizeof(ew));
ew[0]=0;
for(int i=1;i<=E;i++)
{
int u,v,w;
read(u); read(v); read(w);
if(u>v) swap(u,v);
if(v-u!=1) //环上横叉边
{
iskey[u]=iskey[v]=true;
addedge(u,v,w);
addedge(v,u,w);
}
else ew[u]=min(ew[u],w); //环上权值 注意这里是向后延伸的
}
for(int i=1;i<=n;i++)
if(iskey[i]) pt[++pt_cnt]=i; //统计关键点
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+ew[i]; //方便快速确定两环上点之间边权 是个好技巧
int last=0;
for(int i=1;i<=n;i++)
{
if(iskey[i])
{
if(!last) //特判第一个关键点
{
last=i;
continue;
}
addedge(i,last,sum[i-1]-sum[last-1]); //当前关键点与其相邻的连边
addedge(last,i,sum[i-1]-sum[last-1]);
last=i;
}
}
for(int i=1;i<=q;i++)
{
int u,v;
read(u); read(v);
if(u>v) swap(u,v);
insert(u); insert(v);
addedge(u,v,sum[v-1]-sum[u-1]);
addedge(v,u,sum[v-1]-sum[u-1]);
sta[++top]=u; sta[++top]=v;
cout<<spfa(u,v)<<'\n';
Recovery();
}
}
QQ40523591~欢迎一起学习交流~