【AGC002D】Stamp Rally

题目

题目链接:https://atcoder.jp/contests/agc002/tasks/agc002_d
一张连通图,\(q\) 次询问从两个点 \(x\)\(y\) 出发,希望经过的点(不重复)数量等于 \(z\),经过的边最大编号最小是多少。
注意并不是要求找一条连接 \(x\)\(y\) 的路径。
\(n,m,Q\leq 10^5\)

思路

这道题挺多做法的。线段树或者 kruskal 重构树都可以过。所以考虑整体二分。
对于每一个询问有一个很直接的思路就是二分最大边的编号,然后 \(O(n+m)\) check 一下是否可行。总复杂度是 \(O(Q(n+m)\alpha(n))\) 的。
由于这道题不强制在线,所以我们考虑整体二分。具体的,假设我们现在确定了答案在边权 \([l,r]\) 的询问的编号在 \([ql,qr]\),那么我们取 \(mid=\frac{l+r}{2}\),将 \([1,mid]\) 的边所连接的点合并,然后枚举询问判断一下连通快大小是否不小于 \(z\) 即可。
但是这样的话,每次合并的复杂度是 \(O(m)\) 的,区间的数量为 \(O(Q\log Q)\),总复杂度就是 \(O(Qm\log Q\alpha(n))\),不可接受。
发现在分治的时候,区间 \([l,r]\) 会分治到 \([l,mid]\)\((mid,r]\),而此时 \([1,mid]\) 的边是连好的,如果我们往 \([l,mid]\) 走,就要删除 \((\frac{l+mid}{2},mid]\) 的边,否则插入 \((mid,\frac{mid+r}{2}]\) 的边。考虑每条边的贡献,显然每条边最多被删除或插入 \(O(\log Q)\) 次。如果可以支持快速删除插入,那么复杂度就正确了。
所以我们不能路径压缩,采用按秩合并。这样树高保持在 \(O(\log n)\) 级别。然后用一个栈记录下被合并的所有边以及合并顺序,如果要删除一部分边,那么就不断弹出栈顶并还原;否则继续插入。
巧妙的是我们插入边的时候是按照边的编号来依次插入的,所以这样保证了正确性。
时间复杂度 \(O(m\log n\log Q)\)

代码

#include <bits/stdc++.h>
#define mp(x,y,z) make_pair(x,make_pair(y,z))
#define ST first
#define ND second.first
#define RD second.second
using namespace std;

const int N=100010;
int n,m,Q,father[N],siz[N],ans[N];
stack<pair<int,pair<int,int> > > st;

struct edge
{
	int u,v;
}e[N];

struct Query
{
	int x,y,z,id;
}ask[N],b[N];

int find(int x)
{
	return father[x]==x?x:find(father[x]);
}

void merge(int x,int y,int id)
{
	x=find(x); y=find(y);
	if (x==y) return;
	if (siz[x]<siz[y])
	{
		siz[y]+=siz[x]; father[x]=y;
		st.push(mp(id,x,y));
	}
	else
	{
		siz[x]+=siz[y]; father[y]=x;
		st.push(mp(id,y,x));
	}
}

void binary(int l,int r,int ql,int qr)
{
	int mid=(l+r)>>1,p=ql-1,q=qr+1;
	while (st.size() && st.top().ST>mid)
	{
		pair<int,pair<int,int> > t=st.top();
		siz[t.RD]-=siz[t.ND];
		father[t.ND]=t.ND;
		st.pop();
	}
	for (int i=l;i<=mid;i++)
		merge(e[i].u,e[i].v,i);
	for (int i=ql;i<=qr;i++)
	{
		int x=find(ask[i].x),y=find(ask[i].y);
		if (x==y)
		{
			if (siz[x]>=ask[i].z)
				b[++p]=ask[i],ans[ask[i].id]=mid;
			else
				b[--q]=ask[i];
		}
		else
		{
			if (siz[x]+siz[y]>=ask[i].z)
				b[++p]=ask[i],ans[ask[i].id]=mid;
			else
				b[--q]=ask[i];
		}
	}
	for (int i=ql;i<=qr;i++) ask[i]=b[i];
	if (l==r) return;
	binary(l,mid,ql,p); binary(mid+1,r,q,qr);
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
		scanf("%d%d",&e[i].u,&e[i].v);
	scanf("%d",&Q);
	for (int i=1;i<=Q;i++)
	{
		scanf("%d%d%d",&ask[i].x,&ask[i].y,&ask[i].z);
		ask[i].id=i; ans[i]=m;
	}
	for (int i=1;i<=n;i++)
		father[i]=i,siz[i]=1;
	binary(1,m,1,Q);
	for (int i=1;i<=Q;i++)
		printf("%d\n",ans[i]);
	return 0;
}
posted @ 2021-01-04 19:23  stoorz  阅读(77)  评论(0编辑  收藏  举报