[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;
}

posted @ 2025-01-22 10:25  Add_Catalyst  阅读(4)  评论(0)    收藏  举报