1. 程式人生 > 其它 >AT1998 [AGC002D] Stamp Rally

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\)

作為 \(x,y\) 的父親來合併 \(x,y\)\(z\) 的點權為邊 \((x,y)\) 的長度。

最後我們建出來了一棵二叉樹,具體長這樣:

這棵樹擁有以下性質:

  • 葉子節點都是構成最小生成樹的節點。

  • 生成樹中有 \(n\) 個節點,會產生 \(n-1\) 個含有點權的節點,共 \(n+n-1=2\cdot n-1\) 個節點。

  • 按最小生成樹重構的重構樹是大根堆,按最大生成樹重構的重構樹是小根堆。

  • 按最小生成樹重構的重構樹中任意兩點 \(a,b\) 的路徑中的最大邊權為它們 \(\operatorname{LCA}(a,b)\) 的點權,也是 \(a,b\) 路徑中最大邊權的最小值,按最大生成樹重構的重構樹中任意兩點 \(a,b\)

    的路徑中的最小邊權為它們 \(\operatorname{LCA}(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;
}