Kruskal重構樹——[NOI2018] 歸程
感覺 Kruskal 重構樹比較簡單,就不單獨開學習筆記了。
Statement
給定一個 \(n\) 點 \(m\) 邊的無向連通圖,用 \(l,a\) 描述一條邊的長度、海拔。用水位線描述降雨,所有海拔不超過水位線的邊都是有積水的。
有 \(q\) 次詢問,每次給出出發點 \(v\) 和水位線 \(p\) ,開始會有一輛車,可以經過任意沒有積水的邊,可以在任意節點下車步行(車不會延續到下一次)。問回到 \(1\) 號點最小的步行長度。強制線上。
\(n\leq 2e5,m\leq 4e5,q\leq 4e5,1\leq S\leq 1e9,l\leq 1e4,a\leq 1e9\)
Solution
名字挺高階的,事實上很簡單。
重點解決的主要問題: 查詢從某個點出發經過邊權不超過 val 的邊所能到達的節點 。
大致就是類似 點分治=>點分樹 的思路,點分樹是把點分治中所有分治重心按照層次關係連線起來形成樹狀結構,那麼 Kruskal 重構樹就是在 Kruskal 建最小生成樹的時候,不直接合並兩個點,而是新建一個虛點進行合併。好吧,看上去一點都不像
具體一點就看 這個部落格 .
性質:一個點的所有子樹節點的權值都不大於它的權值,並且從它開始逐漸向子節點移動,權值是單調不增的。
Proof
顯然,考慮 Kruskal 的加邊過程即可。
查詢的時候進行樹上倍增,得到能夠到達的最遠祖先,那麼根據限制,能到達的連通塊中的節點就是這個祖先點的子樹中所有的葉節點。(除了葉節點都是虛點嘛)
回到這道題目。題目要求就是要將一條 \(v\to 1\) 的路徑分成兩部分,設斷點為 \(u\) ,要滿足 \(u\to v\) 的路徑上所有邊的海拔大於 \(p\) ,在此前提下 \(1\to u\) 最短。
那麼很容易發現,合法 \(u\) 構成的點集在原圖的最大生成樹上。
根據上面的鋪墊,容易想到使用 Kruskal 重構樹:
把每條邊按照海拔降序,求出重構樹。按照上面的步驟,對於每個詢問,樹上倍增得到包含起點 \(v\) 的子樹中根節點深度最小且海拔大於 \(p\) 的子樹 \(x\) ,那麼合法 \(u\) 的集合就是 \(x\) 子樹內的所有葉子節點。現在就要在這些點中找出到點 \(1\)
總複雜度為 \(\mathcal{O}(T\times n\log n)\) .
Code
過不去 UOJ Extra Test 的多半是沒開 long long.
//Author: RingweEH
const int N=2e5+10;
struct edge
{
int to,nxt,val;
}e[N<<2];
struct node
{
int u; ll dis;
bool operator < ( const node &tmp ) const { return dis>tmp.dis; }
};
struct edge2
{
int fro,to,val;
bool operator < ( const edge2 &tmp ) const { return val>tmp.val; }
}e2[N<<1];
int n,m,q,k,s,tot=0,head[N],fath[N<<1],fa[N<<1][21],a[N<<1],siz;
ll dis[N<<1],lasans;
void add( int u,int v,int w )
{
e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].val=w;
}
void Dijkstra()
{
priority_queue<node> q;
for ( int i=1; i<=n*2-1; i++ )
dis[i]=1e15;
dis[1]=0; q.push( (node){1,0} );
while ( !q.empty() )
{
node u=q.top(); q.pop();
if ( u.dis>dis[u.u] ) continue;
for ( int i=head[u.u]; i; i=e[i].nxt )
if ( u.dis+e[i].val<dis[e[i].to] ) q.push( (node){e[i].to,dis[e[i].to]=u.dis+e[i].val} );
}
}
int find( int x )
{
return x==fath[x] ? x : fath[x]=find(fath[x]);
}
void Kruskal()
{
memset( fa,0,sizeof(fa) ); siz=n;
for ( int i=1; i<n*2; i++ )
fath[i]=i;
sort( e2+1,e2+1+m );
for ( int i=1; i<=m; i++ )
{
int u=find( e2[i].fro ),v=find( e2[i].to );
if ( u==v ) continue;
fa[u][0]=fath[u]=fa[v][0]=fath[v]=++siz;
a[siz]=e2[i].val; dis[siz]=min( dis[u],dis[v] );
if ( siz==n*2-1 ) break;
}
for ( int i=siz; i>=1; i-- )
for ( int j=1; j<=18; j++ )
fa[i][j]=fa[fa[i][j-1]][j-1];
}
int main()
{
freopen( "return.in","r",stdin ); freopen( "return.out","w",stdout );
int T=read();
while ( T-- )
{
memset( head,0,sizeof(head) ); tot=lasans=0;
n=read(); m=read();
for ( int i=1; i<=m; i++ )
{
e2[i].fro=read(),e2[i].to=read(); int w=read(); e2[i].val=read();
add( e2[i].fro,e2[i].to,w ); add( e2[i].to,e2[i].fro,w );
}
q=read(); k=read(); s=read(); Dijkstra(); Kruskal();
while ( q-- )
{
int v=read(),p=read();
v=(v+k*lasans-1)%n+1; p=(p+k*lasans)%(s+1);
for ( int i=17; i>=0; i-- )
if ( fa[v][i] && a[fa[v][i]]>p ) v=fa[v][i];
printf( "%lld\n",dis[v] ); lasans=dis[v];
}
}
fclose( stdin ); fclose( stdout );
return 0;
}