[HNOI2016] 最小公倍数 题解
[HNOI2016] 最小公倍数 题解
题意简述
给定一个 \(n\) 个点,\(m\) 条边的图,每条边 \((u,v)\) 有两个权值 \(a_i,b_i\)。
给定 \(Q\) 个询问 u v a b
:问 \(u,v\) 之间有没有一条路径(可以为非简单路径,但至少经过两个点)满足 \(\max_i a_i = a \land \max_i b_i = b\),其中 \(a_i,b_i\) 是路径上的边的权值。
分析
\(30\%\)
首先链上的部分分可以倍增加二分解决,总时间复杂度为 \(O((n+Q)\log_2{n})\)。
\(20\%\)
然后 \(n \le 1000,m \le 2000\) 的可以在每次询问图上 BFS 或 DFS,总时间复杂度为 \(O(mQ)\)。
\(20\%\)
\(a,b \le 30\) 或 \(a=0\) 的情况都可以分别用并查集解决,复杂度分别为 \(O(am\log_2{n}+Q)\) 和 \(O(m\log_2{n}+Q)\)。
//#define Plus_Cat ""
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(5e4+10),M(1e5+10),lN(17),lV(lN+1);
namespace IOEcat {
//快读快写
} using namespace IOEcat;
bool ans[N];
int n,m,Q;
struct edge {
int u,v,a,b;
} e[M];
struct Queries {
int u,v,a,b,idx;
} Que[N];
struct CFS {
int tot,h[N],deg[N];
struct edge {
int v,a,b,c,nxt;
edge(int v=0,int a=0,int b=0,int c=0,int nxt=-1):v(v),a(a),b(b),c(c),nxt(nxt) {}
} e[M<<1];
edge &operator [](int i) {
return e[i];
}
void Init(int n) {
tot=-1,RCL(h+1,-1,int,n);
}
void att(int u,int v,int a,int b,int c) {
e[++tot]=edge(v,a,b,c,h[u]),h[u]=tot;
}
void con(int u,int v,int a,int b,int c) {
att(u,v,a,b,c),att(v,u,a,b,c),++deg[u],++deg[v];
}
} g;
namespace Subtask1 { /*30%*/ /*List*/
int rt,tot;
int idx[N],Lg[N];
int A[N][lV],B[N][lV];
bool Check() {
return m==n-1;
}
int Max(int l,int r,int A[N][lV]) {
int x(Lg[(r--)-l]);
return max(A[l][x],A[r-(1<<x)+1][x]);
}
void dfs(int u,int fa) {
idx[u]=++tot;
EDGE(g,i,u,v)if(v^fa)A[tot][0]=g[i].a,B[tot][0]=g[i].b,dfs(v,u);
}
int Cmain() {
FOR(i,1,n)if(g.deg[i]==1)rt=i;
dfs(rt,0),Lg[0]=-1;
FOR(i,1,n)Lg[i]=Lg[i>>1]+1;
FOR(j,1,lN)FOR(i,1,n-(1<<j))
A[i][j]=max(A[i][j-1],A[i+(1<<(j-1))][j-1]),B[i][j]=max(B[i][j-1],B[i+(1<<(j-1))][j-1]);
FOR(i,1,Q) {
int L(idx[Que[i].u]),R(idx[Que[i].v]),ansA(0),ansB(0);
if(L>R)swap(L,R);
if(L<R)ansA=Max(L,R,A),ansB=Max(L,R,B);
if(ansA>Que[i].a||ansB>Que[i].b)continue;
for(int l(1),r(L-1),mid((l+r)>>1); l<=r; mid=(l+r)>>1)
Max(mid,L,A)<=Que[i].a&&Max(mid,L,B)<=Que[i].b?
tomax(ansA,Max(mid,L,A)),tomax(ansB,Max(mid,L,B)),r=mid-1:l=mid+1;
for(int l(R+1),r(n),mid((l+r)>>1); l<=r; mid=(l+r)>>1)
Max(R,mid,A)<=Que[i].a&&Max(R,mid,B)<=Que[i].b?
tomax(ansA,Max(R,mid,A)),tomax(ansB,Max(R,mid,B)),l=mid+1:r=mid-1;
ans[i]=(ansA==Que[i].a&&ansB==Que[i].b);
}
FOR(i,1,Q)puts(ans[i]?"Yes":"No");
return 0;
}
}
namespace Subtask2 { /*10%*/ /*BFS*/
bool vis[N];
bool Check() {
return n<=1000&&m<=2000&&Q<=1000;
}
bool BFS(int S,int T,int A,int B) {
int ansA(-1),ansB(-1);
queue<int> q;
RCL(vis+1,0,bool,n),vis[S]=1,q.push(S);
while(!q.empty()) {
int u(q.front());
q.pop();
EDGE(g,i,u,v)if(g[i].a<=A&&g[i].b<=B) {
tomax(ansA,g[i].a),tomax(ansB,g[i].b);
if(!vis[v])vis[v]=1,q.push(v);
}
}
return vis[T]&&ansA==A&&ansB==B;
}
int Cmain() {
FOR(i,1,Q)ans[i]=BFS(Que[i].u,Que[i].v,Que[i].a,Que[i].b);
FOR(i,1,Q)puts(ans[i]?"Yes":"No");
return 0;
}
}
struct DSU {
int fa[N],A[N],B[N];
void Init(int n) {
FOR(i,1,n)fa[i]=i,A[i]=-1,B[i]=-1;
}
int get(int x) {
return fa[x]^x?fa[x]=get(fa[x]):x;
}
void Merge(int u,int v,int a,int b) {
v=get(v),u=get(u),fa[v]=u,tomax(A[u],A[v],a),tomax(B[u],B[v],b);
}
} dsu;
namespace Subtask3 { /*10%*/ /*DSU*/
vector<int> que[35][35],edges[35];
bool Check() {
FOR(i,1,m)if(e[i].a>30||e[i].b>30)return 0;
FOR(i,1,Q)if(Que[i].a>30||Que[i].b>30)return 0;
return 1;
}
int Cmain() {
FOR(i,1,m)edges[e[i].b].push_back(i);
FOR(i,1,Q)que[Que[i].a][Que[i].b].push_back(i);
FOR(a,0,30) {
dsu.Init(n);
FOR(b,0,30) {
for(int x:edges[b])if(e[x].a<=a)dsu.Merge(e[x].u,e[x].v,e[x].a,e[x].b);
for(int x:que[a][b])if(dsu.get(Que[x].u)==dsu.get(Que[x].v)) {
int p(dsu.get(Que[x].u));
ans[x]=(Que[x].a==dsu.A[p]&&Que[x].b==dsu.B[p]);
}
}
}
FOR(i,1,Q)puts(ans[i]?"Yes":"No");
return 0;
}
}
namespace Subtask4 { /*10%*/ /*DSU*/
bool Check() {
FOR(i,1,m)if(e[i].a)return 0;
FOR(i,1,Q)if(Que[i].a)return 0;
return 1;
}
int Cmain() {
int it(1);
dsu.Init(n);
sort(e+1,e+m+1,[](edge x,edge y) { return x.b<y.b; });
sort(Que+1,Que+Q+1,[](Queries x,Queries y) { return x.b<y.b; });
FOR(i,1,Q) {
while(it<=m&&e[it].b<=Que[i].b)dsu.Merge(e[it].u,e[it].v,e[it].a,e[it].b),++it;
if(dsu.get(Que[i].u)==dsu.get(Que[i].v)) {
int p(dsu.get(Que[i].u));
ans[Que[i].idx]=(dsu.A[p]==Que[i].a&&dsu.B[p]==Que[i].b);
}
}
FOR(i,1,Q)puts(ans[i]?"Yes":"No");
return 0;
}
}
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".txt","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
I(n,m),g.Init(n);
FOR(i,1,m)I(e[i].u,e[i].v,e[i].a,e[i].b),g.con(e[i].u,e[i].v,e[i].a,e[i].b,i);
I(Q);
FOR(i,1,Q)I(Que[i].u,Que[i].v,Que[i].a,Que[i].b),Que[i].idx=i;
if(Subtask1::Check())return Subtask1::Cmain();
if(Subtask2::Check())return Subtask2::Cmain();
if(Subtask3::Check())return Subtask3::Cmain();
if(Subtask4::Check())return Subtask4::Cmain();
return 0;
}
\(100\%\)
看到这个数据范围,以及毫无 \(\log{n}\) 算法方面的思路,猜想大概不是 \(\log{n}\) 算法,同时发现前两个部分分的方法似乎可以互补,可能是 \(\sqrt{n}\) 算法。
结合前面两个 \(20\%\) 的部分分,我们尝试对加入的边先按 \(a_i\) 分块,然后把询问分到对应的块中,一个询问的块是满足以下条件的最后一个块:前面的块中边的 \(a_i\) 都小于等于该询问的 \(a\)。
把所有边按 \(b_i\) 排序,然后我们对每个块内分别处理:块内的询问也按 \(b\) 排序,然后遍历询问,同时按顺序尺取处理权值 \(b_i\) 小于等于 \(b\) 的边,将分到前面块的边直接在并查集上连边,然后对于块内没有连上的边,在单个询问时连上进行 BFS 或 DFS 即可。
//#define Plus_Cat ""
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define DE(...) E(#__VA_ARGS__,__VA_ARGS__)
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(5e4+10),M(1e5+10);
bool ans[N],vis[N],used[N];
int n,m,Q,Bl,Bn;
int st[M],en[M],ord[M],sta[N],q[N];
struct edge {
int u,v,a,b;
} e[M];
struct Query {
int u,v,a,b,idx;
};
vector<Query> Que[M];
struct CFS {
int tot,h[N];
struct edge {
int v,a,b,nxt;
edge(int v=0,int a=0,int b=0,int nxt=-1):v(v),a(a),b(b),nxt(nxt) {}
} e[M<<1];
edge &operator [](int i) {
return e[i];
}
void Init(int n) {
tot=-1,RCL(h+1,-1,int,n);
}
void att(int u,int v,int a,int b) {
e[++tot]=edge(v,a,b,h[u]),h[u]=tot;
}
void con(int u,int v,int a,int b) {
att(u,v,a,b),att(v,u,a,b);
}
} g;
struct DSU {
int fa[N],siz[N],A[N],B[N];
void Init(int n) {
FOR(i,1,n)fa[i]=i,siz[i]=1,A[i]=-1,B[i]=-1;
}
int get(int x) {
while(fa[x]^fa[fa[x]])x=fa[x]=fa[fa[x]];
return fa[x];
}
void Merge(int u,int v,int a,int b) {
if((v=get(v))==(u=get(u)))return tomax(A[u],a),tomax(B[u],b),void();
if(siz[u]<siz[v])swap(u,v);
siz[fa[v]=u]+=siz[v],tomax(A[u],A[v],a),tomax(B[u],B[v],b);
}
} dsu;
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".txt","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
/*Input*/
cin>>n>>m,g.Init(n);
FOR(i,1,m)cin>>e[i].u>>e[i].v>>e[i].a>>e[i].b;
sort(e+1,e+m+1,[](edge x,edge y) { return x.a<y.a; });
iota(ord+1,ord+m+1,1),sort(ord+1,ord+m+1,[](int x,int y) { return e[x].b<e[y].b; });
cin>>Q,Bl=ceil(1.5*m/sqrt(Q)),Bn=(m-1)/Bl+1;
FOR(i,1,Bn)st[i]=en[i-1]+1,en[i]=min(en[i-1]+Bl,m);
/*Query*/
FOR(i,1,Q) {
int res(1);
Query que;
cin>>que.u>>que.v>>que.a>>que.b,que.idx=i;
for(int l(2),r(Bn),mid((l+r)>>1); l<=r; mid=(l+r)>>1)e[en[mid-1]].a<=que.a?res=mid,l=mid+1:r=mid-1;
Que[res].push_back(que);
}
/*Solve*/
FOR(i,1,Bn) {
int it(1);
dsu.Init(n),sort(Que[i].begin(),Que[i].end(),[](Query x,Query y) { return x.b<y.b; });
for(const Query &que:Que[i]) {
/*Insert*/
while(it<=m&&e[ord[it]].b<=que.b) {
if(ord[it]<st[i])dsu.Merge(e[ord[it]].u,e[ord[it]].v,e[ord[it]].a,e[ord[it]].b);
++it;
}
/*Build*/
int top(0),u(dsu.get(que.u)),v(dsu.get(que.v));
used[sta[++top]=u]=1;
if(u^v)used[sta[++top]=v]=1;
FOR(j,st[i],en[i]) {
if(e[j].a>que.a)break;
if(e[j].b<=que.b) {
int u(dsu.get(e[j].u)),v(dsu.get(e[j].v));
if(!used[u])used[sta[++top]=u]=1;
if(!used[v])used[sta[++top]=v]=1;
g.con(u,v,e[j].a,e[j].b);
}
}
/*BFS*/
int h(1),t(0),mA(-1),mB(-1);
vis[q[++t]=u]=1;
while(h<=t) {
int u(q[h++]);
tomax(mA,dsu.A[u]),tomax(mB,dsu.B[u]);
EDGE(g,i,u,v) {
tomax(mA,g[i].a),tomax(mB,g[i].b);
if(!vis[v])vis[q[++t]=v]=1;
}
}
ans[que.idx]=vis[v]&&mA==que.a&&mB==que.b;
/*Clear*/
g.tot=-1;
FOR(i,1,top)g.h[sta[i]]=-1,used[sta[i]]=vis[sta[i]]=0;
}
}
/*Output*/
FOR(i,1,Q)puts(ans[i]?"Yes":"No");
return 0;
}