AT1998 [AGC002D] Stamp Rally
題目大意
一張連通圖,\(q\) 次詢問從兩個點 \(x\) 和 \(y\) 出發,希望經過的點(不重複)數量等於 \(z\),經過的邊最大編號最小是多少。
題目分析
什麼是 \(\rm Kruskal\) 重構樹
從下面的例子入手:
\(\rm Kruskal\) 最小生成樹演算法都知道吧 \(\verb!qwq!\),該例最小生成樹為:
我們按照 \(\rm Kruskal\) 的方式建樹,設要連線的邊為 \((u,v)\),通過並查集可求得 \(u\) 的祖先節點為 \(x\),\(v\) 的祖先節點為 \(y\)。
若 \(x\neq y\),則新建一個節點 \(z\)
最後我們建出來了一棵二叉樹,具體長這樣:
這棵樹擁有以下性質:
-
葉子節點都是構成最小生成樹的節點。
-
生成樹中有 \(n\) 個節點,會產生 \(n-1\) 個含有點權的節點,共 \(n+n-1=2\cdot n-1\) 個節點。
-
按最小生成樹重構的重構樹是大根堆,按最大生成樹重構的重構樹是小根堆。
-
按最小生成樹重構的重構樹中任意兩點 \(a,b\) 的路徑中的最大邊權為它們 \(\operatorname{LCA}(a,b)\) 的點權,也是 \(a,b\) 路徑中最大邊權的最小值,按最大生成樹重構的重構樹中任意兩點 \(a,b\)
模板程式碼:
namespace ex_Kruskal{ // 注意陣列開兩倍! int nowidx,idx; inline bool cmp1(Node x,Node y) { // 按最小生成樹重構的重構樹 return x.w < y.w; } inline bool cmp2(Node x,Node y) { // 按最大生成樹重構的重構樹 return x.w > y.w; } inline void add(int u,int v) { gra[++ idx].v = v,gra[idx].nxt = head[u]; head[u] = idx; } inline void Kruskal() { for (register int i = 1;i <= n * 2 - 1; ++ i) { fa[i] = i; } sort (node + 1,node + m + 1,ex_Kruskal::cmp); nowidx = n; for (register int i = 1;i <= m; ++ i) { int x = getf(node[i].u),y = getf(node[i].v); if (x != y) { val[++ nowidx] = node[i].w;//儲存當前節點的點權 fa[x] = fa[y] = nowidx; add(nowidx,x),add(nowidx,y); } } } }
應用
本題我們可以二分編號(顯然滿足單調性),對於詢問 \(x,y\),我們倍增向上跳到點權大於當前二分值的位置,然後再判斷此時 \(x,y\) 跳到的節點 \(x',y'\) 的子樹中的葉子節點數之和是否達到了 \(z\)。
如果沒有達到 \(z\),說明經過的點的數量少了,應該調大一點,即 \(l\gets mid+1\);如果超過了 \(z\),說明應該調小一點,即 \(r\gets mid-1\);如果等於 \(z\),那麼此時的 \(mid\) 即為答案。
程式碼
//2022/2/8
//2022/2/9
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <algorithm>
#define enter() putchar(10)
#define debug(c,que) cerr<<#c<<" = "<<c<<que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i=(st);i<=(ed);i++)cout<<arr[i]<<w;
#define speed_up() cin.tie(0),cout.tie(0)
#define endl "\n"
#define Input_Int(n,a) for(register int i=1;i<=n;i++)scanf("%d",a+i);
#define Input_Long(n,a) for(register long long i=1;i<=n;i++)scanf("%lld",a+i);
#define mst(a,k) memset(a,k,sizeof(a))
namespace Newstd
{
inline int read()
{
int x=0,k=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-')
{
k=-1;
}
ch=getchar();
}
while(ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*k;
}
inline void write(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
{
write(x/10);
}
putchar(x%10+'0');
}
}
using namespace Newstd;
using namespace std;
const int ma=1e5+5;
struct Gragh
{
int v;
int nxt;
};
Gragh gra[ma<<1];
int head[ma<<1],fa[ma<<1],val[ma<<1],sons[ma<<1];
int fath[ma<<1][21];
int n,m,q;
int nowidx,idx;
inline void add(int u,int v)
{
gra[++idx].v=v;
gra[idx].nxt=head[u];
head[u]=idx;
}
inline void dfs(int now,int dad)
{
fath[now][0]=dad;
for(register int i=1;i<=20;i++)
{
fath[now][i]=fath[fath[now][i-1]][i-1];
}
if(head[now]==0)
{
sons[now]=1;
return;
}
for(register int i=head[now];i;i=gra[i].nxt)
{
int v=gra[i].v;
if(v!=dad)
{
dfs(v,now);
sons[now]+=sons[v];
}
}
}
namespace dsu
{
inline int getf(int x)
{
return fa[x]==x?x:fa[x]=getf(fa[x]);
}
}
namespace ex_Kruskal
{
inline void Kruskal()
{
for(register int i=1;i<=n*2-1;i++)
{
fa[i]=i;
}
val[0]=INT_MAX;
nowidx=n;
for(register int i=1;i<=m;i++)
{
int u=read(),v=read();
int x=dsu::getf(u),y=dsu::getf(v);
if(x!=y)
{
val[++nowidx]=i;
fa[x]=fa[y]=nowidx;
add(nowidx,x),add(nowidx,y);
}
}
}
}
namespace bs
{
inline bool check(int now,int x,int y,int num)
{
for(register int i=20;i>=0;i--)
{
if(val[fath[x][i]]<=now)
{
x=fath[x][i];
}
if(val[fath[y][i]]<=now)
{
y=fath[y][i];
}
}
if(x==y)
{
return sons[x]<num;
}
return sons[x]+sons[y]<num;
}
}
int main(void)
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
n=read(),m=read();
ex_Kruskal::Kruskal();
dfs(nowidx,0);
q=read();
while(q--)
{
int u=read(),v=read(),w=read();
int l=1,r=m;
int ans;
while(l<=r)
{
int mid=l+r>>1;
if(bs::check(mid,u,v,w)==true)
{
l=mid+1;
}
else
{
r=mid-1;
ans=mid;
}
}
write(ans);
enter();
}
return 0;
}