[HNOI2016]最小公倍数
Description:
给你一个图,每条边有\(a_i,b_i\)两个属性,每次询问两个点间有没有一条路径满足路径上的点\(max_{a_i}=x_i,max_{b_i}=y_i\)
Hint:
\(n,q\le 5*10^4,q\le 10^5\)
Solution:
有点毒瘤....
这种题一般要考虑把询问离线排序,消除其中一个量的影响,然后枚举另一个量
所以我们把询问按\(b_i\)排序,按\(b_i\)从小到大把边依次加入图中,用并查集维护
但这样对\(a_i\)的枚举还是\(O(n)\)的,所以总复杂度\(O(n^2)\)
考虑把复杂度均摊
先把\(a_i\)按大小分块,我们加\(b_i\)时,给所有块内最小值大于对应\(a_i\)的块更新
每次查询再去按\(a_i\)从小到大枚举一部分"漏更新"的边角料\(a_i\)
这样每次修改\(\sqrt{n}\),枚举也是最多\(\sqrt{n}\),这样复杂度就降下来了
因为每次要撤销边角料的修改,所以用按秩合并的并查集+栈来搞
#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int mxn=1e5+500;
int n,m,ans[mxn],hd[mxn];
inline int read() {
char c=getchar(); int x=0,f=1;
while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
while(c<='9'&&c>='0') {x=(x<<3)+(x<<1)+(c&15);c=getchar();}
return x*f;
}
inline void chkmax(int &x,int y) {if(x<y) x=y;}
inline void chkmin(int &x,int y) {if(x>y) x=y;}
int size,k,blk[mxn];
struct Line {
int u,v,a,b,id;
friend bool operator < (Line x,Line y) {
return x.a<y.a;
}
}t[mxn],T[mxn],q[mxn];
int cmp1(Line x,Line y) {
return x.a<y.a;
}
int cmp2(Line x,Line y) {
return x.b<y.b;
}
struct ifm {
int type,x,y,z;
}stk[mxn];
int top;
struct Un {
int fa[mxn],sz[mxn],mxa[mxn],mxb[mxn];
int find(int x) {
return fa[x]==0?x:find(fa[x]);
}
void merge(int x,int y,int a,int b,int opt) {
int u=find(x),v=find(y);
if(sz[u]>sz[v])
swap(x,y),swap(u,v);
if(opt==1)
stk[++top]=(ifm){1,v,mxa[v],mxb[v]};
chkmax(mxa[v],max(mxa[u],a));
chkmax(mxb[v],max(mxb[u],b));
if(u==v) return ;
fa[u]=v; sz[v]+=sz[u]; //按秩合并,这样才能回溯,且不能路径压缩
if(opt==1)
stk[++top]=(ifm){0,u,v,sz[u]};
}
void backdate() {
while(top) {
if(stk[top].type==0)
fa[stk[top].x]=0,sz[stk[top].y]-=stk[top].z;
else
mxa[stk[top].x]=stk[top].y,
mxb[stk[top].x]=stk[top].z;
--top;
}
}
int query(int x,int y,int a,int b) {
int anc=find(x);
if(x==y&&a==0&&b==0) return sz[anc]!=1; //判自环
if(anc!=find(y)) return 0;
if(mxa[anc]!=a||mxb[anc]!=b) return 0;
return 1;
}
}st[1005];
int main()
{
n=read(); m=read();
for(int i=1;i<=m;++i)
t[i].u=read(),t[i].v=read(),t[i].a=read(),t[i].b=read(),T[i]=t[i];
k=read();
for(int i=1;i<=k;++i)
q[i].u=read(),q[i].v=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
int size=int(sqrt(m*20)),cnt=m/size;
for(int i=0;i<=cnt;++i)
for(int j=1;j<=n;++j)
st[i].sz[j]=1;
sort(t+1,t+m+1,cmp1); sort(T+1,T+m+1,cmp1);
for(int i=0;i<=cnt;++i) blk[i]=t[i*size].a;
sort(q+1,q+k+1,cmp2); sort(t+1,t+m+1,cmp2); int pos=1;
for(int i=1;i<=k;++i) {
while(t[pos].b<=q[i].b&&pos<=m) {
int bg=lower_bound(blk,blk+cnt+1,t[pos].a)-blk;
//找到这条边该插的最小的块
for(int j=bg;j<=cnt;++j)
st[j].merge(t[pos].u,t[pos].v,t[pos].a,t[pos].b,0);
++pos;
}
int x=upper_bound(blk,blk+cnt+1,q[i].a)-blk-1; //找到询问的a对应的块
Line tp; tp.a=blk[x]; int tpos=upper_bound(T+1,T+m+1,tp)-T;
while(T[tpos].a<=q[i].a&&tpos<=m) { //更新零碎的a关键字边
if(T[tpos].b<=q[i].b)
st[x].merge(T[tpos].u,T[tpos].v,T[tpos].a,T[tpos].b,1);
++tpos;
}
ans[q[i].id]=st[x].query(q[i].u,q[i].v,q[i].a,q[i].b);
st[x].backdate(); //撤回插入的a关键字边
}
for(int i=1;i<=k;++i) ans[i]==1?puts("Yes"):puts("No");
return 0;
}