2018.07.18 [NOI2018]歸程(return)(kruskal重構樹)
傳送門
新鮮出爐的noi2018試題。
下面講講這題的解法:
首先要學習一個叫做kruskal重構樹的東東。
聽名字就知道跟kruskal演算法有關,沒錯,原來的kruskal演算法就是用並查集實現的,但當我們使用kruskal重構樹的時候,對於每次找出的不同的兩個連通塊的祖先,我們都新建一個點作為兩個祖先的父親,並將當前邊的邊權轉化為新點的點權。然而,路徑壓縮的時候會讓我們丟失這種辛辛苦苦創造的樹的形狀。。。因此我們需要在使用並查集維護連通性的同時使用二叉樹來維護樹的形狀。這樣維護出來的樹就是kruskal重構樹。
不難發現kruskal重構樹有幾條重要的性質:
1.樹上除葉子結點以外的點都對應著原來生成樹中的邊,葉子結點就是原來生成樹上的節點。
2.由於新點的建立順序與原來生成樹上邊權的大小有關,可以發現,從每個點到根節點上除葉子結點外按順序訪問到的點的點權是單調的。
3.出於kruskal演算法貪心的性質,兩個點u和v的lca的點權就對應著它們最小生成樹上的瓶頸。
4.實際上這棵樹就是一個二叉堆
所以這道題如何用krukal重構樹做呢?
如果我們以海拔為第一關鍵字對邊進行從大到小的排序,然後修建kruskal重構樹,這樣就弄出了一顆以海拔為關鍵字的小根堆。然後對於每一棵子樹,如果詢問中的水位線是低於子樹的根節點的,那麼此時這棵子樹中的所有葉子結點都是連通的。放到題中就是說這顆子樹中任選一個點出發,到子樹中的其它點都不需要花費。
然後我們假設對於當前詢問,我們找到了一個子樹的根節點u,滿足d[u]>p且d[fa[u]]<=p且出發點v在子樹中,這時從v出發可以直接抵達子樹中的任意一個葉子結點。因此我們需要從眾多葉子節點中選出一個距離1號點花費最小的。
然後再捋一捋思路。我們首先要求出每個點到1號點的最小花費,這個直接dijstra+最短路預處理。然後是要建出kruskal重構樹,再然後維護以每個點作為根節點時子樹中距離1號點的最小花費,這個建完樹後一個簡單的dfs搞定。最後是如何找到點u,這時我們要讓一個重要的演算法登場:倍增演算法。直接加上點權>p的限制在樹上倍增即可。
總時間複雜度O(T*nlogn)。
程式碼如下:
#include<bits/stdc++.h>
#define N 400005
#define M 800005
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
inline void write(int x){
if(x>9)write(x/10);
putchar(x%10+'0');
}
int n,m,T,q,k,s,vis[N],first[N<<1],head[N],cntx=0,d[N],dep[N],f[N][20],fa[N<<1],lastans=0,totx=0;
struct Node{int u,v,l,a;}e[M],p[N<<1];
struct edge{int v,next;}tr[M<<1];
struct node{int v,next,w;}t[M];
struct heap{int u,v;};
inline bool operator<(heap a,heap b){return a.v>b.v;}
inline void dijstra(int s=1){
memset(vis,false,sizeof(vis));
memset(d,0x3f,sizeof(d));
priority_queue<heap>q;
d[s]=0;
q.push((heap){s,d[s]});
while(!q.empty()){
heap x=q.top();
q.pop();
if(vis[x.u])continue;
vis[x.u]=true;
for(int i=head[x.u];i;i=t[i].next){
int v=t[i].v;
if(vis[v])continue;
if(d[v]>d[x.u]+t[i].w){
d[v]=d[x.u]+t[i].w
;
q.push((heap){v,d[v]});
}
}
}
for(int i=1;i<=n;++i)p[i].l=d[i];
}
inline bool cmp(Node a,Node b){return a.a>b.a;}
inline int find(int x){return x==fa[x]?fa[x]:fa[x]=find(fa[x]);}
inline void add(int u,int v){
tr[++cntx].v=v;
tr[cntx].next=first[u];
first[u]=cntx;
}
inline void addx(int u,int v,int w){
t[++totx].v=v;
t[totx].next=head[u];
t[totx].w=w;
head[u]=totx;
}
inline void dfs(int u,int pa){
dep[u]=dep[pa]+1,f[u][0]=pa;
for(int i=1;i<=19;++i)f[u][i]=f[f[u][i-1]][i-1];
for(int i=first[u];i;i=tr[i].next){
int v=tr[i].v;
dfs(v,u);
p[u].l=min(p[u].l,p[v].l);
}
}
inline int query(int x,int y){
for(int i=19;i>=0;--i)if(dep[x]-(1<<i)>0&&p[f[x][i]].a>y)x=f[x][i];
return p[x].l;
}
inline void kruskal(){
int tot=0,cnt=n;
for(int i=1;i<=(n<<1);++i)fa[i]=i;
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;++i){
int u=e[i].u,v=e[i].v;
int fx=find(u),fy=find(v);
if(fx!=fy){
add(++cnt,fx);
add(cnt,fy);
fa[fx]=cnt;
fa[fy]=cnt;
p[cnt].a=e[i].a;
++tot;
}
if(tot==n-1)break;
}
dfs(cnt,0);
while(q--){
int x=(k*lastans+read()-1)%n+1,y=(k*lastans+read())%(s+1);
write(lastans=query(x,y));
puts("");
}
}
int main(){
T=read();
while(T--){
lastans=0,n=read(),m=read();
memset(e,0,sizeof(e)),cntx=0,totx=0;
memset(first,0,sizeof(first));
memset(head,0,sizeof(head));
memset(f,0,sizeof(f));
for(int i=1;i<=m;++i)e[i].u=read(),e[i].v=read(),e[i].l=read(),e[i].a=read(),addx(e[i].u,e[i].v,e[i].l),addx(e[i].v,e[i].u,e[i].l);
for(int i=n+1;i<=(n<<1);++i)p[i].l=0x3f3f3f3f;
dijstra();
q=read(),k=read(),s=read();
kruskal();
}
return 0;
}