NOI 2018 Day1 T1 归程
难点: 走过有积水的地方之后就需计算路径长了
关键算法: kruskal重构树
①原来的 kruskalkruskalkruskal 算法就是用并查集实现的,
但当我们使用 kruskal重构树的时候,
对于每次找出的不同的两个连通块的祖先,
我们都新建一个点作为两个祖先的父亲,并将当前边的边权转化为新点的点权(或指向该点的边的边权)。
②因为kruskal是贪心加边,所以对于该题来说,
如果在重构树上能从一个点抵达另一个点,那么在原图上也一定可以
③如果我们以海拔为第一关键字对边进行从大到小的排序,然后修建 kruskal重构树,
这样就弄出了一颗以海拔为关键字的小根堆。
然后对于每一棵子树,如果询问中的水位线是低于子树的根节点的,
那么此时这棵子树中的所有叶子结点都是连通的。
放到题中就是说这颗子树中任选一个点出发,
到子树中的其它点都不需要花费。(此段来自洛谷题解)
④对于每个询问,我们只需要找到该点无需花费就能走到的点(用预处理好的倍增找)中,哪个离目的地(1号点)更近,
这个预处理一下最短路就是了
上代码:
#include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #define rg register #define _ 200001 #define a(x) edge[x].a #define l(x) edge[x].l #define u(x) heap[x].u #define it(x) heap[x].it #define min(x,y) x<y?x:y; using namespace std; int n,m,record[_<<1],exist[_],num_of_edges,an[_<<1][22],minn[_<<1][22],cnt,dad[_<<1]; long long dis[_],New_dis[_<<1]; bool used[_]; struct ppp { int x,y,a; }way[_<<1]; struct pp { int next,to,l,a; }edge[_<<3]; struct pppp { int u,it; }heap[800001]; inline int read() { rg int save=0,w=1;rg char q=getchar(); while(q<'0'||q>'9'){if(q=='-')w=-1;q=getchar();} while(q>='0'&&q<='9')save=(save<<3)+(save<<1)+q-'0',q=getchar(); return save*w; } inline void add(rg int from,rg int to,rg int ll,rg int aa) { edge[++num_of_edges]=(pp){record[from],to,ll,aa}; record[from]=num_of_edges; } int tail; inline void put(rg int i,rg int ww) { u(++tail)=i,it(tail)=ww; rg int d=tail; while(d>1) { if(it(d)<it(d>>1))swap(heap[d],heap[d>>1]),d>>=1; else break; } } inline void cut() { heap[1]=heap[tail--]; it(tail+1)=2147483647; rg int d=1; while(d<tail) { rg int pointer=it(d<<1)<it(d<<1|1)?(d<<1):(d<<1|1); if(it(d)>it(pointer))swap(heap[d],heap[pointer]),d=pointer; else break; } } int find(rg int x){if(x!=dad[x])dad[x]=find(dad[x]);return dad[x];} inline void dijkstra() { for(rg int i=1;i<=n;++i)used[i]=0; dis[1]=0; put(1,dis[1]); rg int k=0; while(tail) { rg pppp ss=heap[1]; cut(); if(used[ss.u])continue; used[ss.u]=1; k++; for(rg int j=record[ss.u];j;j=edge[j].next) { rg int to=edge[j].to; if(dis[to]>dis[ss.u]+edge[j].l) { dis[to]=dis[ss.u]+edge[j].l; put(to,dis[to]); } } if(k==n-1)break; } } inline bool Cwen(rg ppp x,rg ppp y){return x.a>y.a;} int ceng; void dfs(rg int); inline void Kruskal() { rg int i,j; for(i=1;i<=(n<<1);++i)dad[i]=i; sort(way+1,way+m+1,Cwen); for(i=1;i<=m;++i) { rg int fx=find(way[i].x),fy=find(way[i].y); if(fx!=fy)//重构树 { add(++cnt,fx,0,way[i].a);//(++cnt):新建节点 add(cnt,fy,0,way[i].a);//因为之后的操作只需要从根节点遍历下来,故不建反向边 dad[fx]=dad[fy]=cnt; if(cnt==(n<<1)-1)break; } } ceng=log(cnt)/log(2); for(i=1;i<=cnt;++i) { if(i<=n)New_dis[i]=dis[i]; for(j=1;j<=ceng;++j) an[i][j]=0,minn[i][j]=2147483647; } for(i=n+1;i<=cnt;++i)New_dis[i]=2147483647; //这个题目里不要找LCA,depth[]也就不需要 an[cnt][0]=0;//聊胜于无的一句话 dfs(cnt);//预处理点之间的最小海拔 } void dfs(rg int i) { for(rg int j=1;j<=ceng;++j) { an[i][j]=an[an[i][j-1]][j-1]; minn[i][j]=min(minn[i][j-1],minn[an[i][j-1]][j-1]); } if(i<=n)return;//免得走到原来的图上了(在重构树里<=n的就是叶子节点,无需继续遍历) for(rg int j=record[i];j;j=edge[j].next) { rg int to=edge[j].to; if(to!=an[i][0]) { an[to][0]=i; minn[to][0]=a(j); dfs(to); New_dis[i]=min(New_dis[i],New_dis[to]); } } } inline int jump(rg int i,rg int p) { for(rg int j=ceng;j>=0;--j) if(minn[i][j]>p)i=an[i][j]; return i; } int main() { rg int t=read(); while(t--) { n=read(),m=read(); rg int i,j; tail=0; for(i=1;i<=n;++i)dis[i]=2147483647; for(i=1;i<=(n<<2);++i)it(i)=2147483647; num_of_edges=0; for(i=1;i<=(n<<1);++i)record[i]=0; for(i=1;i<=m;++i) { rg int u=read(),v=read(),l=read(),a=read(); add(u,v,l,a),add(v,u,l,a); way[i]=(ppp){u,v,a}; } dijkstra(); cnt=n; Kruskal();//重构树,以海拔为关键字从大到小排序,保证可以判断一个点是否能被无耗遍历到 rg int Q=read(),K=read(),S=read(); long long lastans=0; for(i=1;i<=Q;++i) { rg int v=read(),p=read(); v=(v+K*lastans-1)%n+1; p=(p+K*lastans)%(S+1); rg int to=jump(v,p); lastans=New_dis[to]; printf("%lld\n",lastans); } } return 0; }