题意:给你一张n个点m条边的带权无向图。其中由s个点是加油站。询问从x加油站到y加油站,油箱容量<=b,能否走到?
n,m,q,s<=20W,b<=2e9。
标程:
1 #include<cstdio> 2 #include<algorithm> 3 #include<queue> 4 #include<cstring> 5 using namespace std; 6 typedef long long ll; 7 int read() 8 { 9 int x=0;char ch=getchar(); 10 while (ch<'0'||ch>'9') ch=getchar(); 11 while ('0'<=ch&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); 12 return x; 13 } 14 const int N=200005; 15 const int inf=0x3f3f3f3f; 16 struct node{int to,next,w;}num[N*2]; 17 struct _node{int i;ll dis;_node(int A,ll B){i=A;dis=B;}}; 18 struct Node{int u,v,id;ll dis;Node(){}; Node(int A,int B,ll C,int D){u=A;v=B;dis=C;id=D;}}a[N],e[N]; 19 struct cmp{ 20 bool operator () (const _node &A,const _node &B) 21 {return A.dis>B.dis;} 22 }; 23 priority_queue<_node,vector<_node>,cmp>q; 24 int cnt,S,fa[N],u,v,w,n,s,m,head[N],vis[N],f[N],Q,ans[N],tot; 25 ll dis[N],b; 26 void add(int x,int y,int w) 27 {num[++cnt].to=y;num[cnt].next=head[x];num[cnt].w=w;head[x]=cnt;} 28 int find(int x) {return x==f[x]?x:f[x]=find(f[x]);} 29 bool operator < (const Node &A,const Node &B){return A.dis<B.dis;} 30 void dijk() 31 { 32 while (!q.empty()) 33 { 34 _node now=q.top();q.pop(); 35 if (vis[now.i]) continue;vis[now.i]=1; 36 for (int i=head[now.i];i;i=num[i].next) 37 if (dis[num[i].to]>dis[now.i]+num[i].w) 38 { 39 dis[num[i].to]=dis[now.i]+num[i].w;q.push(_node(num[i].to,dis[num[i].to])); 40 fa[num[i].to]=fa[now.i]; 41 } 42 } 43 } 44 void init() 45 { 46 for (int i=1;i<=n;i++) 47 for (int j=head[i];j;j=num[j].next) 48 if (fa[i]!=fa[num[j].to]&&fa[i]<fa[num[j].to]) e[++tot]=Node(fa[i],fa[num[j].to],dis[i]+dis[num[j].to]+num[j].w,tot); 49 } 50 void mst() 51 { 52 int head=1; 53 for (int i=1;i<=n;i++) f[i]=i; 54 for (int i=1;i<=Q;i++) 55 { 56 while (head<=tot&&e[head].dis<=a[i].dis) 57 { 58 int x=e[head].u,y=e[head].v; 59 if (find(x)!=find(y)) f[find(x)]=find(y); 60 head++; 61 } 62 if (find(a[i].u)!=find(a[i].v)) ans[a[i].id]=0;else ans[a[i].id]=1; 63 } 64 } 65 int main() 66 { 67 n=read();s=read();m=read(); 68 memset(dis,inf,sizeof(dis)); 69 for (int i=1;i<=s;i++) S=read(),dis[S]=0,q.push(_node(S,0)),fa[S]=S; 70 for (int i=1;i<=m;i++) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w); 71 dijk();init(); 72 Q=read(); 73 for (int i=1;i<=Q;i++) a[i].u=read(),a[i].v=read(),a[i].dis=read(),a[i].id=i; 74 sort(a+1,a+Q+1);sort(e+1,e+tot+1); 75 mst(); 76 for (int i=1;i<=Q;i++) puts(ans[i]?"TAK":"NIE"); 77 return 0; 78 }
题解:并查集+技巧
要走到肯定是瞄准了加油站走。而且每次如果能走,往最近的加油站走答案一定不会变劣(你也可以走回来)。
那么就有一种高超的建图方法:
1.跑多源最短路,找到每个点离它最近的加油站,算出距离。
2.对于原图边(u,v),如果nearest[u]!=nearest[v],那么连边nearest[u]-nearest[v],边权为dist(u,nearest[u])+dist(v,nearest[v])+dist(u,v)。
这样离u,v最近的加油站就有经过(u,v)的一条路径了,但是不一定这样的dist(nearest[u],nearest[v])就是实际最短路,然而如果不是实际最短路,一定有另一个加油站k,离这边一个加油站更近,而且这样nearest[u]和nearest[v]一定通过另外几个加油站连通,实际走的策略也应该是经过另外几个加油站的。
离线掉询问按b从小到大排序,也按照b的顺序加入边,用并查集维护连通性。查询时,起点终点在同一个块中则可行。O((q+n)a)。
需要在线的话就构造出最小生成树,倍增/树剖找链上最大值。