【BZOJ4537】最小公倍数(HNOI2016)-分块+并查集启发式合并
测试地址:最小公倍数
做法:本题需要用到分块+并查集启发式合并。
脱去题目各种疑似数论的包装之后,我们发现实际上题目让你求的是:存不存在一条路径,使得路径上最大的和最大的分别为询问的和。显然如果只有一个询问的话,我们就把并且的边连起来,再用并查集维护联通块内最大的和最大的,如果询问的两个点连通并且最大的和分别等于和,那就存在这样一条路径,否则就不存在。那么现在的问题是怎么把的这些边取出来。
考虑两种暴力:
1.每次扫一遍所有的边,将所有的边连起来,用并查集维护。
2.将边按排序,将询问按排序,每次处理一个询问时,将的边按从小到大排序,处理对应的边。
我们将这两种暴力折中起来,成为一个分块算法,具体如下:
将边按排序,将询问按排序,然后将边分块,对于每一块,处理在这一块内的询问,做法就是:对于前面所有块中的边,因为在前面的块中必然,那么这些边只要在询问的到某个程度的时候是一定会被加入的,所以我们把这些边按排序,这样就能保证前面块中的边在处理每一块时只会处理一次。对于块内的边,暴力扫一遍将符合条件的边全部处理。注意在处理一个新的询问之前,我们还要将一些操作撤销,所以并查集不能路径压缩,那么就必须使用启发式合并来保证时间复杂度,那么我们就得到了一个复杂度为的算法。实践证明(实际上是抄的题解),块大小为时是最优的。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,q,blocksiz,changed[50010],s[50010];
bool vis[50010]={0},ans[50010]={0};
struct node
{
int fa,mxa,mxb,h;
}a[50010],ori[50010];
struct edge
{
int u,v,a,b,id;
}e[100010],Q[50010];
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d%d%d%d",&Q[i].u,&Q[i].v,&Q[i].a,&Q[i].b);
Q[i].id=i;
}
}
bool cmp(edge a,edge b)
{
if (a.a!=b.a) return a.a<b.a;
else return a.b<b.b;
}
bool cmpb(edge a,edge b)
{
return a.b<b.b;
}
int find(int x)
{
while(x!=a[x].fa) x=a[x].fa;
return x;
}
void change(int x)
{
vis[x]=1;
changed[++changed[0]]=x;
ori[x]=a[x];
}
void merge(int x,int y,int i,bool type)
{
int fx=find(x),fy=find(y);
if (a[fx].h>a[fy].h) swap(fx,fy);
if (type&&!vis[fx]) change(fx);
if (type&&!vis[fy]) change(fy);
if (fx==fy)
{
a[fx].mxa=max(a[fx].mxa,e[i].a);
a[fx].mxb=max(a[fx].mxb,e[i].b);
return;
}
a[fx].fa=fy;
a[fy].mxa=max(max(a[fx].mxa,a[fy].mxa),e[i].a);
a[fy].mxb=max(max(a[fx].mxb,a[fy].mxb),e[i].b);
a[fy].h=max(a[fy].h,a[fx].h+1);
}
void work()
{
blocksiz=sqrt(m);
sort(e+1,e+m+1,cmp);
sort(Q+1,Q+q+1,cmpb);
int top;
for(int i=1;i<=m;i+=blocksiz)
{
for(int j=1;j<=n;j++)
{
a[j].fa=j;
a[j].mxa=a[j].mxb=-1;
a[j].h=1;
}
sort(e+1,e+i,cmpb);
top=0;
for(int j=1;j<=q;j++)
if (Q[j].a>=e[i].a&&(i+blocksiz>m||Q[j].a<e[i+blocksiz].a))
s[++top]=j;
int nowp=1;
for(int k=1;k<=top;k++)
{
int nowq=s[k];
while(nowp<i&&e[nowp].b<=Q[nowq].b)
{
merge(e[nowp].u,e[nowp].v,nowp,0);
nowp++;
}
changed[0]=0;
for(int j=i;j<=min(i+blocksiz-1,m);j++)
if (e[j].a<=Q[nowq].a&&e[j].b<=Q[nowq].b)
merge(e[j].u,e[j].v,j,1);
int fx=find(Q[nowq].u),fy=find(Q[nowq].v);
ans[Q[nowq].id]=(fx==fy&&a[fx].mxa==Q[nowq].a&&a[fx].mxb==Q[nowq].b);
for(int j=1;j<=changed[0];j++)
{
a[changed[j]]=ori[changed[j]];
vis[changed[j]]=0;
}
}
}
}
void output()
{
for(int i=1;i<=q;i++)
{
if (ans[i]) printf("Yes\n");
else printf("No\n");
}
}
int main()
{
init();
work();
output();
return 0;
}