可以想到一种朴素的做法:
对于每个询问,我二分答案,然后将编号小于答案的边都加入并查集,最后判断一下u和v所在的连通块大小之和是否大于z就可以了(注意如果u和v在一个连通块中只能算一次),这样的复杂度是
O(q∗n∗logn)
O
(
q
∗
n
∗
l
o
g
n
)
,需要优化
考虑这个方法的效率瓶颈,主要是我们对各个询问进行计算的时候,很多次二分的答案都是一样的,对于这样二分出同样答案的状态我们分开考虑,就要多建很多次并查集,效率显然不高
所以我们考虑整体二分
刚开始,所有询问的答案区间都在1~n中,我们对所有的询问都二分出一个答案mid,构建好并查集以后,所有的询问都用这个并查集来check,必然有的询问check通过,有的不通过,这样第二轮的时候我就有一些答案区间在1~mid,另一些答案区间在mid+1~r的询问,再对两种询问分别二分答案,以此类推递归下去
这样的复杂度貌似是炸的,因为虽然二分的过程只有log层,但这个二分过程向两遍都有扩展,理论上最坏会二分出
O(n)
O
(
n
)
级别个数的答案,每一次都要建立并查集,复杂度退化到了
O(n2)
O
(
n
2
)
但是我们注意到我们在每一层,每一个阶段,其实只需要建一次并查集,因为比如这一层有若干种答案区间二分出的若干个答案
mid1
m
i
d
1
,
mid2
m
i
d
2
…
midn
m
i
d
n
,假设
mid1<mid2<...<midn
m
i
d
1
<
m
i
d
2
<
.
.
.
<
m
i
d
n
,那么做完
mid1
m
i
d
1
之后,我们可以在
mid1
m
i
d
1
的并查集的基础上继续加边来获得
mid2
m
i
d
2
的并查集,这样从左向右做,我们可以看出每一层并查集加边数量最多为
O(m)
O
(
m
)
,一共有log层,所以总复杂度为
O(mlogn)
O
(
m
l
o
g
n
)
这里再口胡一个
O(qn−−√)
O
(
q
n
)
的做法
我们把答案分成
n−−√
n
个块,然后我们用每个块的边界去判定每个询问的答案在哪个块里面,这一步的复杂度是
O(qn−−√)
O
(
q
n
)
然后我们对于每个块内的询问,从小到大枚举答案,并对属于该块的所有询问check,这个check的过程和二分答案中check的过程是一样的,这样每个块的复杂度是
O(rin−−√)
O
(
r
i
n
)
,因为
∑n√i=1ri=q
∑
i
=
1
n
r
i
=
q
,所以这第二步的复杂度也是
O(qn−−√)
O
(
q
n
)
的,这样总复杂度是
O(qn−−√)
O
(
q
n
)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;
const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;
inline int getint()
{
char ch;int res;bool f;
while (!isdigit(ch=getchar()) && ch!='-') {}
if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
while (isdigit(ch=getchar())) res=res*10+ch-'0';
return f?res:-res;
}
int n,e,m;
Pair edge[100048];
int ans[100048];
struct node
{
int x,y;
int needsz;
int Left,Right;
int ind;
inline void init(int xx) {x=getint();y=getint();needsz=getint();Left=1;Right=e;ind=xx;}
inline bool operator < (const node &x) const
{
return ((Left+Right)>>1)<((x.Left+x.Right)>>1);
}
}q[100048];
namespace DSU
{
int pre[100048],rnk[100048];
inline void init()
{
for (int i=1;i<=n+10;i++) pre[i]=i,rnk[i]=1;
}
inline int find_anc(int x)
{
if (pre[x]!=x) pre[x]=find_anc(pre[x]);
return pre[x];
}
inline void update(int x,int y)
{
x=find_anc(x);y=find_anc(y);
if (x==y) return;
pre[x]=y;rnk[y]+=rnk[x];
}
inline int getsz(int x) {return rnk[find_anc(x)];}
}
int main ()
{
int i,layer,curedge,totsz;
n=getint();e=getint();
for (i=1;i<=e;i++) edge[i].x=getint(),edge[i].y=getint();
m=getint();
for (i=1;i<=m;i++) q[i].init(i);
for (layer=1;layer<=20;layer++)
{
sort(q+1,q+m+1);
DSU::init();curedge=0;
for (i=1;i<=m;i++)
{
int mid=((q[i].Left+q[i].Right)>>1);
while (curedge<mid)
{
curedge++;
DSU::update(edge[curedge].x,edge[curedge].y);
}
if (q[i].Left==q[i].Right) continue;
totsz=DSU::getsz(q[i].x)+DSU::getsz(q[i].y);
if (DSU::find_anc(q[i].x)==DSU::find_anc(q[i].y)) totsz>>=1;
if (totsz>=q[i].needsz) q[i].Right=mid; else q[i].Left=mid+1;
}
}
for (i=1;i<=m;i++) ans[q[i].ind]=q[i].Left;
for (i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}