Kruskal重構樹 - 歸程
阿新 • • 發佈:2022-03-18
P4768 [NOI2018] 歸程
核心思想:
以海拔為第一關鍵字對邊進行從大到小的排序,然後修建kruskal重構樹,這樣就弄出了一顆以海拔為關鍵字的小根堆。然後對於每一棵子樹,如果詢問中的水位線是低於子樹的根節點的,那麼此時這棵子樹中的所有葉子結點都是連通的。
將高海拔邊建在靠近葉子節點的地方 , 使得回跳時如果一個節點滿足海拔,則其整顆子樹均滿足海拔要求,即滿足了回跳時海拔的單調性。放到題中就是說這顆子樹中任選一個點出發,到子樹中的其它點都不需要花費(路的海拔均高於該點,可以隨便通行),即該子樹中的節點對答案都不會做出貢獻。
對於一個節點U,如果前往節點1的道路海拔低於水位線,則他必須在此地下車,一路步行回點1,距離即為U到點1的最短路。所以,我們只需要O(nlogn)跑一遍Dijkstra,便可在之後O(1)求得任意點U到1的最短距離,即在U點下車後走路的距離。
對於每一組詢問,我們只需要找到一個離節點1最近(dis值最小)的最後可行節點(再往上走一層,海拔節點的海拔就不再滿足要求的節點)該點到節點1的距離(即dis值)即為最小步行距離。
程式碼解析:
定義宣告:
int n,m,lastans;
struct node
{
int u,v,nex,len/*路徑長度*/,hig/*海拔*/;
}e[N2]/*暫時儲存邊資訊,後轉移到長度節點上*/,tmp[N2]/*儲存重構後的樹*/,edge[N2]/*儲存原圖*/;
Dijkstra:
初始化節點1到每個節點的距離,方便此後O(1)求距離。
int dis[N]; priority_queue <pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q; inline void dijkstra() { memset(dis,127,sizeof(dis)); dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()) { int u=q.top().second; if(dis[u]<q.top().first) {q.pop();continue;}//如果該點已經被更新過,則該點的dis值<q.top().second,退出(假如在加入一次過後又進行了一次加入,則後一次加入明顯更優,此時len值也相應地進行了更新,等同於當前最優解的第二項的數值,而小於第一次第二項的數值,故再次執行到該點時就會滿足條件直接退出) q.pop(); for(int i=fir[u];i;i=edge[i].nex) { int v=edge[i].v; if(dis[v]>dis[u]+edge[i].len) { dis[v]=dis[u]+edge[i].len; q.push(make_pair(dis[v],v)); } } } }
Kruskal:
將海拔並著原圖點一同建到新樹中。
int fa[N];//並查集 int getf(int v) { return fa[v]==v?v:fa[v]=getf(fa[v]); } inline bool cmp(node xx,node yy) { return xx.hig>yy.hig; } int cnt;//節點編號 inline void Kruskal() { fr(i,n)fa[i]=i;//重新初始化fa[] cnt=n; int num=0; sort(e+1,e+m+1,cmp);//按海拔將邊從大到小排序(將高海拔邊建在靠近葉子節點的地方,使得回跳時如果一個節點滿足海拔,則其整顆子樹均滿足海拔要求,即滿足了回跳時海拔的單調性) fr(i,m)//列舉所有邊 { int x=e[i].u,y=e[i].v; int fx=getf(x),fy=getf(y); if(fx^fy)//如果尚未聯通 { num++;//統計新建節點數 tmp[++cnt].hig=e[i].hig;//將原本位於邊上的資訊轉移到長度節點上 fa[fx]=fa[fy]=fa[cnt]=cnt;//將兩節點父親(同時也將兩節點)聯通 (同時重置cnt的fa值為自己) Link(cnt,fx),Link(cnt,fy);//重構樹連邊 } if(num==n-1)break;//優化:最多新建n-1個長度節點就可以把所有的點聯通(因為最小生成樹的末狀態是所有點聯通,故只需x-1個節點將它們相連) } }
Dfs:
求解新樹中每個節點的深度,給Query()做準備。
int f[N][21]/*f[x][y]:x節點往上跳2^y步所在的位置*/,dep[N];
inline void Dfs(int x,int lst)//求dep[]
{
dep[x]=dep[lst]+1,f[x][0]=lst/*標記往上跳一層的位置lst*/;
fr(i,logN)
f[x][i]=f[f[x][i-1]][i-1];//每個點往上跳2^i的長度的位置等同於往上2^i-1的位置再往上跳2^(i-1)步
for(int i=fir[x];i;i=edge[i].nex)
{
int v=edge[i].v;
Dfs(v,x);
tmp[x].len=min(tmp[x].len,tmp[v].len);//尋求每天的最小路程,能更新就進行更新
}
}
Query:
從節點U出發往上跳,找到海拔高度大於p(水位線)的len值最小的點,即滿足條件(高於水位線)中的最小海拔節點(亦即最短路徑)。
inline int query(int x,int p)//從x(出發節點)出發往上跳,找到海拔大於p(水位線)的len值最小的點
{
for(int i=logN;i>=0;--i)
{
if(dep[x]>(1<<i)/*如果深度大於2^i,即能跳的話*/ && tmp[f[x][i]].hig>p/*且海拔高於水位線*/)
{
x=f[x][i];//就往上跳
}
}
return tmp[x].len;//即滿足條件(高於水位線)中的最小長度節點(即最短路徑)
}
Solve:
inline void solve()
{
memset(fir,0,sizeof(fir));
rot=0;
Kruskal();
Dfs(cnt,0);//為什麼是cnt:cnt是新樹中的最後一個節點,同時也是 最上面的 節點,即樹根
int q=rd(),k=rd(),s=rd();
while(q--)
{
int x=(k*lastans+rd()-1)%n+1,p=(k*lastans+rd())%(s+1);
cout<<(lastans=query(x,p))<<'\n';
}
}
Main:
int main()
{
int T=rd();
while(T--)
{
init();
n=rd(),m=rd();
fr(i,m)
{
e[i].u=rd(),e[i].v=rd(),e[i].len=rd(),e[i].hig=rd();
Link(e[i].u,e[i].v,e[i].len),Link(e[i].v,e[i].u,e[i].len);
}
dijkstra();//預處理出所有點到節點1的最短距離
fr(i,n)tmp[i].len=dis[i];//並將其儲存到新圖中的原圖節點上
for(int i=n+1;i<=(n<<1);i++)tmp[i].len=Inf;//把長度節點len值設為 Inf,稍後查詢走路距離最小值的時候就會把長度節點剔除掉,只考慮原圖節點
solve();
}
return 0;
}
完整程式碼:
#include<bits/stdc++.h>
#define fr(i,r) for(int i=1;i<=r;++i)
using namespace std;
const int N=4e5+10,N2=8e5+10,Inf=0x7fffffff,logN=19;
char cch;
int res;
inline int rd()
{
while((cch=getchar())<48);
res=cch^48;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res;
}
int n,m,lastans;
struct node
{
int u,v,nex,len/*路徑長度*/,hig/*海拔*/;
}e[N2]/*暫時儲存邊資訊,後轉移到長度節點上*/,tmp[N2]/*儲存重構後的樹*/,edge[N2]/*儲存原圖*/;
int fir[N],rot;
inline void Link(int u,int v,int w=0)
{
edge[++rot].nex=fir[u];
fir[u]=rot;
edge[rot].v=v;
edge[rot].len=w;
}
int dis[N];
priority_queue <pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
inline void dijkstra()
{
memset(dis,127,sizeof(dis));
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty())
{
int u=q.top().second;
if(dis[u]<q.top().first) {q.pop();continue;}//如果該點已經被更新過,則該點的dis值<q.top().second,退出(假如在加入一次過後又進行了一次加入,則後一次加入明顯更優,此時len值也相應地進行了更新,等同於當前最優解的第二項的數值,而小於第一次第二項的數值,故再次執行到該點時就會滿足條件直接退出)
q.pop();
for(int i=fir[u];i;i=edge[i].nex)
{
int v=edge[i].v;
if(dis[v]>dis[u]+edge[i].len)
{
dis[v]=dis[u]+edge[i].len;
q.push(make_pair(dis[v],v));
}
}
}
}
int fa[N];//並查集
int getf(int v)
{
return fa[v]==v?v:fa[v]=getf(fa[v]);
}
inline bool cmp(node xx,node yy)
{
return xx.hig>yy.hig;
}
int cnt;//節點編號
inline void Kruskal()
{
fr(i,n)fa[i]=i;//重新初始化fa[]
cnt=n;
int num=0;
sort(e+1,e+m+1,cmp);//按海拔將邊從大到小排序(將高海拔邊建在靠近葉子節點的地方,使得回跳時如果一個節點滿足海拔,則其整顆子樹均滿足海拔要求,即滿足了回跳時海拔的單調性)
fr(i,m)//列舉所有邊
{
int x=e[i].u,y=e[i].v;
int fx=getf(x),fy=getf(y);
if(fx^fy)//如果尚未聯通
{
num++;//統計新建節點數
tmp[++cnt].hig=e[i].hig;//將原本位於邊上的資訊轉移到長度節點上
fa[fx]=fa[fy]=fa[cnt]=cnt;//將兩節點父親(同時也將兩節點)聯通 (同時重置cnt的fa值為自己)
Link(cnt,fx),Link(cnt,fy);//重構樹連邊
}
if(num==n-1)break;//優化:最多新建n-1個長度節點就可以把所有的點聯通(因為最小生成樹的末狀態是所有點聯通,故只需x-1個節點將它們相連)
}
}
int f[N][21],dep[N];
inline void Dfs(int x,int lst)//求dep[]
{
dep[x]=dep[lst]+1,f[x][0]=lst/*標記往上跳一層的位置lst*/;
fr(i,logN)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=fir[x];i;i=edge[i].nex)
{
int v=edge[i].v;
Dfs(v,x);
tmp[x].len=min(tmp[x].len,tmp[v].len);//尋求每天的最小路程,能更新就進行更新
}
}
inline int query(int x,int y)//從x(出發節點)出發往上跳,找到海拔大於y(水位線)的len值最小的點
{
for(int i=logN;i>=0;--i)
{
if(dep[x]>(1<<i)/*如果深度大於2^i,即能跳的話*/ && tmp[f[x][i]].hig>y/*且海拔高於水位線*/)
{
x=f[x][i];//就往上跳
}
}
return tmp[x].len;//即滿足條件(高於水位線)中的最小長度節點(即最短路徑)
}
inline void solve()
{
Kruskal();
Dfs(cnt,0);
int q=rd(),k=rd(),s=rd();
while(q--)
{
int x=(k*lastans+rd()-1)%n+1,y=(k*lastans+rd())%(s+1);
cout<<(lastans=query(x,y))<<'\n';
}
}
inline void init()
{
memset(fir,0,sizeof(fir));
memset(fa,0,sizeof(fa));
memset(f,0,sizeof(f));
memset(tmp,0,sizeof(tmp));
memset(edge,0,sizeof(edge));
lastans=rot=0;
}
int main()
{
int T=rd();
while(T--)
{
init();
n=rd(),m=rd();
fr(i,m)
{
e[i].u=rd(),e[i].v=rd(),e[i].len=rd(),e[i].hig=rd();
Link(e[i].u,e[i].v,e[i].len),Link(e[i].v,e[i].u,e[i].len);
}
dijkstra();//預處理出所有點到節點1的最短距離
fr(i,n)tmp[i].len=dis[i];//並將其儲存到新圖中的原圖節點上
for(int i=n+1;i<=(n<<1);i++)tmp[i].len=Inf;//把長度節點len值設為 Inf,稍後查詢走路距離最小值的時候就會把長度節點剔除掉,只考慮原圖節點
memset(fir,0,sizeof(fir));
rot=0;
solve();
}
return 0;
}