bzoj 4537: [Hnoi2016]最小公倍数

一部血泪史:
开始我想到分块,但是一直在想直接按照a的权值分块,却发现无论怎么做复杂度都不对。
看题解,发现直接把边分块就行。
题解:
考虑暴力做法,就是把所有<=a且<=b的边全部用并查集维护,记录每个连通块ab的最大值。
想到这个做法,不难想到分块, 把边按照a为第一关键字,b为第二关键字排序,分成若干块。
每次把前i块的边和第i块的询问按照b的大小排序,按照b的权值从小到大把边加入并查集,注意如果这条边的a权值>这一块的询问的最小a权值,那么不加入,而是开一个队列记录这些边,这些边最多有块的大小个。
然后对于每个询问,把队列里a<=询问的a的加入并查集,查询结束后删除之。
所以这个要用可以删边的启发式合并不带路径压缩的并查集。
坑点:
1 0个数的最小公倍数不是1,所以ab的初值要设-1
2 这道题我的块大小设了sqrt(nlogn)才过,如果是sqrt(n)会T。
   
   
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 1e9
#define eps 1e-8
#define md
#define N 50010
using namespace std;
struct PP { int x,y,a,b,id;} p[150010];
struct List { int son,fa,a,b,sz;} lst[150010];
int fa[N],sz[N],mx_a[N],mx_b[N],ans[50010],st[150010],g[150010];
int find(int x) { while (fa[x]) x=fa[x]; return x;}
bool cmp_A (const PP &a,const PP &b)
{
if (a.a!=b.a) return a.a<b.a;
if (a.b!=b.b) return a.b<b.b;
return a.id<b.id;
}
 
bool cmp_B (int a,int b)
{
if (p[a].b!=p[b].b) return p[a].b<p[b].b;
return p[a].id<p[b].id;
}
void link(int id)
{
int f1=find(p[id].x),f2=find(p[id].y);
if (sz[f1]>sz[f2]) swap(f1,f2);
lst[id].fa=f2;
lst[id].a=mx_a[f2]; lst[id].b=mx_b[f2]; lst[id].sz=sz[f2];
mx_a[f2]=max(max(p[id].a,mx_a[f1]),mx_a[f2]);
mx_b[f2]=max(max(p[id].b,mx_b[f1]),mx_b[f2]);
if (f1==f2) lst[id].son=0;
else lst[id].son=f1,fa[f1]=f2,sz[f2]+=sz[f1];
}
 
void cut(int id)
{
int x=lst[id].son,y=lst[id].fa;
if (x) fa[x]=0;
mx_a[y]=lst[id].a; mx_b[y]=lst[id].b; sz[y]=lst[id].sz;
}
 
int main()
{
//freopen("data.in","r",stdin); freopen("data.out","w",stdout);
int n,m,Q;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i].a,&p[i].b);
scanf("%d",&Q);
for (int i=1;i<=Q;i++)
{
scanf("%d%d%d%d",&p[i+m].x,&p[i+m].y,&p[i+m].a,&p[i+m].b);
p[i+m].id=i;
}
sort(p+1,p+m+1,cmp_A);
int K=sqrt(m*log2(m)),Kn=(m-1)/K+1;
for (int i=1;i<=m;i+=K) p[i].id=-1;
sort(p+1,p+m+Q+1,cmp_A);
int now=0,nn=0;
for (int i=1;i<=m+Q;i++) g[i]=i;
for (;now<m+Q;)
{
now++; g[++nn]=now;
int A=p[now].a;
while (now<m+Q&&p[now+1].id!=-1) { now++; g[++nn]=now;}
sort(g+1,g+nn+1,cmp_B);
int top=0;
for (int j=1;j<=n;j++) fa[j]=0,mx_a[j]=-1,mx_b[j]=-1,sz[j]=1;
for (int j=1;j<=nn;j++)
if (p[g[j]].id>0)
{
int x=g[j];
for (int k=1;k<=top;k++) if (p[x].a>=p[st[k]].a) link(st[k]);
int f1=find(p[x].x),f2=find(p[x].y);
if (f1==f2&&mx_a[f1]==p[x].a&&mx_b[f1]==p[x].b) ans[p[x].id]=1;
else ans[p[x].id]=-1;
for (int k=top;k;k--) if (p[x].a>=p[st[k]].a) cut(st[k]);
}
else
{
if (p[g[j]].a>A) st[++top]=g[j];
else link(g[j]);
}
int nnn=0;
for (int i=1;i<=nn;i++) if (p[g[i]].id<1) g[++nnn]=g[i];
nn=nnn;
}
for (int i=1;i<=Q;i++)
if (ans[i]==1) printf("Yes\n");
else printf("No\n");
return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值