luoguP3247 [HNOI2016]最小公倍数 题解
题意
给定一张 个顶点 条边的无向图(顶点编号为 ),每条边上带有权值。所有权值都可以分解成 的形式。
现在有 个询问,每次询问给定四个参数 和 ,请你求出是否存在一条顶点 到 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 。
注意:路径可以不是简单路径。
,,。
思路
一步一步来。
暴力
先分析给定的条件:满足经过边边权的 为 。那我们可以拆解成:每条边有 属性和 属性,所经过边的 属性最大值为 ,且 必须出现过。 属性同理。
那么暴力想法随即而出:预处理出每个联通块内的边,记录到桶中。对于每一组询问,将不符合的边删掉,在新图上跑 BFS,记录目标两点能否联通,且经过边是否符合条件,判断并输出。复杂度 。
正解
运用到经典思想:离线,边加边询问。则考虑分块。对于每条边先按照第一维排序,将其分为若干个块,处理出块尾元素的 值,记为 数组。对于每一组询问,按照第二维排序。在 数组中查找对应的 值应当所在的块,将询问加入该块的待处理序列中。此时,对于每一组块中的询问,已经保证了 元素一定合法,只需要暴力判断 元素的合法性即可。由于加边是一次性的,处理下次询问时需要撤回影响,所以需要使用到可撤销并查集。复杂度 。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define db double
#define mkp make_pair
#define pb push_back
#define P pair<int,int>
#define _ 0
const int N=1e5+10,mod=1e9+7,MOD=1e9+123,inf=1e18;
int T=1,n,m,Q,L[N],R[N],b[N],ans[N],fa[N],son[N],mxa[N],mxb[N],top;
vector<int> v[N];
struct node{
int u,v,son,a,b,id;
}e[N],q[N],sta[N];
bool cmp1(node x,node y){
return x.a<y.a;
}
bool cmp2(node x,node y){
return x.b<y.b;
}
void mem(){
for(int i=1;i<=n;i++){
fa[i]=i;
son[i]=0;
mxa[i]=mxb[i]=-1;
}
}
int find(int x){
return x==fa[x]?x:find(fa[x]);
}
void add(int u,int v,int a,int b){
int fau=find(u),fav=find(v);
if(son[fau]<son[fav]) swap(fau,fav);
sta[++top]=(node){fau,fav,son[fau],mxa[fau],mxb[fau],0};
mxa[fau]=max(mxa[fau],a),mxb[fau]=max(mxb[fau],b);
if(fau==fav) return ;
fa[fav]=fau,son[fau]=max(son[fau],son[fav]+1);
mxa[fau]=max(mxa[fau],mxa[fav]),mxb[fau]=max(mxb[fau],mxb[fav]);
}
void del(int id){
fa[sta[id].v]=sta[id].v;
mxa[sta[id].u]=sta[id].a;
mxb[sta[id].u]=sta[id].b;
son[sta[id].u]=sta[id].son;
}
void solve(){
cin>>n>>m;
int p=sqrt(1.0*m*log(m));
int siz=m/p;
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].a>>e[i].b;
}
sort(e+1,e+1+m,cmp1);
for(int i=1;i<=siz;i++){
L[i]=(i-1)*p+1,R[i]=i*p;
b[i]=e[L[i]].a;
}
sort(b+1,b+1+siz);
R[siz]=max(m,R[siz]);
cin>>Q;
for(int i=1;i<=Q;i++){
cin>>q[i].u>>q[i].v>>q[i].a>>q[i].b;
q[i].id=i;
}
sort(q+1,q+1+Q,cmp2);
for(int i=1;i<=Q;i++){
int k=upper_bound(b+1,b+1+siz,q[i].a)-b;
k--;
if(!k) ans[q[i].id]=0;
else v[k].pb(i);
}
for(int i=1;i<=siz;i++){
mem();
int head=1;
if(i!=1) sort(e+1,e+L[i],cmp2);
for(int x:v[i]){
while(head<=L[i]&&e[head].b<=q[x].b){
add(e[head].u,e[head].v,e[head].a,e[head].b);
head++;
}
top=0;
for(int j=L[i];j<=R[i];j++){
if(e[j].a<=q[x].a&&e[j].b<=q[x].b){
add(e[j].u,e[j].v,e[j].a,e[j].b);
}
}
int u=find(q[x].u),v=find(q[x].v);
if(u==v&&mxa[u]==q[x].a&&mxb[u]==q[x].b){
ans[q[x].id]=1;
}
while(top) del(top--);
}
}
for(int i=1;i<=Q;i++){
if(ans[i]) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(false),cin.tie(0);
while(T--){
solve();
}
return ~~(0^_^0);
}
总结
本题还算有迹可循。遇到暴力已经无法优化的时候,通常考虑数据结构或者转更暴力的想法:分块。离线排序+边加边询问 是常见的经典技巧,多积累经验才能成熟运用。
作者:ryder
出处:https://www.cnblogs.com/ryder/p/17406874.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通