title
Description
给定一张 \(N\) 个顶点 \(M\) 条边的无向图(顶点编号为 \(1,2,…,n\) ),每条边上带有权值。所有权值都可以分解成 \(2^a*3^b\) 的形式。
现在有 \(q\) 个询问,每次询问给定四个参数 \(u、v、a、b\) ,请你求出是否存在一条顶点 \(u\) 到 \(v\) 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 \(2^a*3^b\) 。
注意:路径可以不是简单路径。
下面是一些可能有用的定义:
最小公倍数: \(K\) 个数 \(a_1,a_2,…,a_k\) 的最小公倍数是能被每个 \(a_i\) 整除的最小正整数。
路径:路径 \(P:P_1,P_2,…,P_k\) 是顶点序列,满足对于任意 \(1\leqslant i<k\) ,节点 \(P_i\) 和 \(P_{i+1}\) 之间都有边相连。
简单路径:如果路径 \(P:P_1,P_2,…,P_k\) 中,对于任意 \(1\leqslant s\not=t\leqslant k\) 都有 \(P_s\not=P_t\) ,那么称路径为简单路径。
Input
输入文件的第一行包含两个整数 \(N\) 和 \(M\) ,分别代表图的顶点数和边数。
接下来 \(M\) 行,每行包含四个整数 \(u、v、a、b\) 代表一条顶点 \(u\) 和 \(v\) 之间、权值为 \(2^a*3^b\) 的边。
接下来一行包含一个整数 \(q\) ,代表询问数。接下来 \(q\) 行,每行包含四个整数 \(u、v、a、b\) ,代表一次询问。
询问内容请参见问题描述。\(1\leqslant n,q\leqslant 50000、1\leqslant m\leqslant 100000、0\leqslant a,b\leqslant 10^9\)
Output
对于每次询问,如果存在满足条件的路径,则输出一行
Yes
,否则输出一行No
(注意:第一个字母大写,其余字母小写) 。
Sample Input
4 5
1 2 1 3
1 3 1 2
1 4 2 1
2 4 3 2
3 4 2 2
5
1 4 3 3
4 2 2 3
1 3 2 2
2 3 2 2
1 3 4 4
Sample Output
Yes
Yes
Yes
No
No
analysis
把 \(a\) 从小到大排序,然后分块。
把每个询问搞到 \(q_a\) 被第 \(i\) 个块所有的边的 \(a_e\) 构成的区间包含的块中(如有多个,搞到最后一个)
然后把在 \(i\) 之前的所有边和当前区间中搞的询问按照 \(b\) 从小到大排序。
然后从前往后一次处理操作,如果有边就加入到并查集中(维护连通性,以及每个连通块中 \(a\) 和 \(b\) 的最大值)。
如果是询问的话,暴力将当前块内合法的边(也就是两维都不超过当前询问)加入即可,然后最后记得需要把所有加入的边撤回。
所以这个并查集不能路径压缩,我真是个傻子,都看到了带权可撤销并查集了,结果还是顺手写了个路径压缩,呜呜。
块的大小是个问题,因为我们要在块内进行过多的 \(sort\) ,可能导致常数巨大,然后...
大概把块的大小调成 \(\sqrt{n\log n}\) 还是挺可以的,复杂度瞎算 \(O(n\sqrt{n\log n})\) ,为啥说是瞎算,这里把 \(n,m\) 当同级来想了(好像也不是这个原因丫),然而好像之前见到有人说:实测,\(O(n\sqrt{n\log n})\) 不一定能跑得过 \(O(n\sqrt{n}\log n)\) ,这个,我就不太了解了(纯当我瞎说了),毕竟这题的复杂度跟块的大小太有关系了 。
code
#include<bits/stdc++.h>
const int maxn=1e5+1e3;
typedef int iarr[maxn];
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
}
using IO::read;
template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }
template<typename T>inline T min(T a,T b) { return a<b ? a : b; }
template<typename T>inline T max(T a,T b) { return a>b ? a : b; }
struct Orz
{
int x,y,a,b,k;
inline void Get(int k_=0)
{
read(x), read(y), read(a), read(b), k=k_;
}
} E[maxn], Q[maxn], ask[maxn];
inline bool cmpa(Orz x,Orz y) { return x.a<y.a || (x.a==y.a && x.b<y.b); }
inline bool cmpb(Orz x,Orz y) { return x.b<y.b || (x.b==y.b && x.a<y.a); }
namespace Union_Set
{
Orz Stack[maxn];
iarr mxa,mxb,height,fa; int top;
inline int get(int x)
{
return fa[x]==x ? x : get(fa[x]);//并查集不能路径压缩,否则无法撤销回去
}
inline Orz merge(int x,int y,int a,int b)
{
x=get(x), y=get(y);
if (height[x]>height[y]) std::swap(x,y);
Orz tmp=(Orz){x,y,mxa[y],mxb[y],height[y]};
if (height[x]==height[y]) ++height[y];
fa[x]=y;
chkMax(mxa[y],max(mxa[x],a));
chkMax(mxb[y],max(mxb[x],b));
return tmp;
}
inline void del()
{
Orz cur=Stack[top--];
fa[cur.x]=cur.x, mxa[cur.y]=cur.a, mxb[cur.y]=cur.b, height[cur.y]=cur.k;
}
}
iarr Beg,End, Mina,Maxa, bel,id,ans;
int main()
{
int n,m;read(n);read(m);
int block=(int)(sqrt(n*log2(m)))*0.6;
for (int i=1; i<=m; ++i) E[i].Get();
std::sort(E+1,E+m+1,cmpa);
for (int i=1; i<=m; ++i)
{
id[i]=i/block+1;
if (id[i]!=id[i-1]) Mina[id[i]]=Maxa[id[i]]=E[i].a, Beg[id[i]]=i, End[id[i-1]]=i-1;
else chkMin(Mina[id[i]],E[i].a), chkMax(Maxa[id[i]],E[i].a);
}
End[id[m]]=m;
int q;read(q);
for (int i=1; i<=q; ++i)
{
Q[i].Get(i);
for (int j=id[m]; j>=1; --j)
if (Mina[j]<=Q[i].a && Q[i].a<=Maxa[j]) { bel[i]=j; break; }
}
using namespace Union_Set;
for (int i=1; i<=m; ++i) if (id[i]!=id[i-1])
{
int cnt=0;
for (int j=1; j<=q; ++j)
if (bel[j]==id[i]) ask[++cnt]=Q[j];
if (!cnt) continue;
std::sort(E+1,E+i,cmpb);
std::sort(ask+1,ask+cnt+1,cmpb);
for (int j=1; j<=n; ++j) fa[j]=j, mxa[j]=mxb[j]=-1;//初始化,因为 0 个数的 lcm 不是 1,所以初值为 -1
int L=1, R=1;
while ( R<i || L<=cnt)
{
if ( (R<i && ask[L].b>=E[R].b) || L>cnt) merge(E[R].x,E[R].y,E[R].a,E[R].b), ++R;
else
{
for (int j=Beg[id[i]]; j<=End[id[i]]; ++j)
if (ask[L].b>=E[j].b && ask[L].a>=E[j].a) Stack[++top]=merge(E[j].x,E[j].y,E[j].a,E[j].b);
int x=get(ask[L].x);
if (x==get(ask[L].y)) ans[ask[L].k]=mxa[x]==ask[L].a && mxb[x]==ask[L].b;
while (top) del();
++L;//Wrong Reason
}
}
}
for (int i=1; i<=q; ++i) puts(ans[i] ? "Yes" : "No");
return 0;
}
summary
对于求两位偏序需要满足条件的题,常常可以分块然后配合可撤销数据结构来解决。
然后可以调整块大小优化复杂度?
然而,这其实不算是我的总结,不过也是我的学习了,见 队爷 \(blog\)