「HNOI2016」最小公倍数

链接

loj

一道阔爱的分块

题意

边权是二元组(A, B),每次询问u, v, a, b,求u到v是否存在一条简单路径,使得各边权上\(A_{max} = a, B_{max} = b\)

分析

对于这种有两种限制的题目

一般的套路就是条件按照第一种权值为关键字排序,询问按照第二种关键字排序

然后给条件分块,然后对于一个块只把第一关键字符合条件的询问放进去

在把当前块前面的整块里的点按照第二关键字排序

这样当前块前面的点都是符合当前询问点对于第一关建字条件的

而且第二关键字都是单调的,所以扫一下

然后对于每个询问,暴力处理一下当前块的贡献

[参考BeNoble_] (https://blog.csdn.net/benoble_/article/details/79777757)

对于这道题 如果暴力怎么做?

对于询问u, v, a, b

把所有满足A<= a, B <= b的边加进来

因为只要最大值,所以可以维护一个带权并查集(find的时候不更新father哦)

(小声:其实这个带权并查集就像一个树一样

然后查询一下是否连通,连通的话所在并查集最大权满不满足条件(即Amax == a && Bmax == b)

所以说,分块的本质都是暴力

然后复杂度就在这个加边上了

就像最前面说的那样加 加完把不整块的删了 okk

框架就像这样

询问按b排序 边按a排序
 
for(对于每一个块){
    收集a大小在该块范围内的询问

    按b排前面整块的点(这样后面就单调了
    
            初始化并查集

    for(对于每一个询问){
        加前面整块里 b满足条件的边(a必然满足条件)

        加不整块的a,b都满足条件的边
        
        判定是否联通且满足条件
        
        还原不整块的边
    }
}

最后附上代码

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector> 
#include <set>
using namespace std;
const int N = 1e5 + 5;

int n, m, qsize;
struct E{
    int u, v, a, b, id;
}e[N], q[N];
struct Opt{
    int u, v, a, b, fa, size;
}opt[N];
bool ans[N];
int blsize;
int b[N], bcnt, tim;
int fa[N], mxa[N], mxb[N], size[N];

bool rulea(E x, E y){return x.a < y.a;}
bool ruleb(E x, E y){return x.b < y.b;}

int find(int x){
    return x == fa[x] ? x : find(fa[x]); 
}

inline void merge(E x, bool type){
    x.u = find(x.u); x.v = find(x.v);
    if(size[x.u] > size[x.v]) swap(x.u, x.v);
    //printf("do %d %d %d\n", x.u, x.v, type);
    if(type) {
        ++tim;
        opt[tim].u = x.u, opt[tim].v = x.v; opt[tim].fa = fa[x.u];
        opt[tim].a = mxa[x.v], opt[tim].b = mxb[x.v];
        opt[tim].size = size[x.v];
    }
    
    if(find(x.u) == find(x.v)){
        mxa[x.v] = max(mxa[x.v], x.a);
        mxb[x.v] = max(mxb[x.v], x.b);
    }
    else {// u -> v
        fa[x.u] = x.v;
        size[x.v] += size[x.u];
        mxa[x.v] = max(mxa[x.v], x.a);
        mxb[x.v] = max(mxb[x.v], x.b);
        mxa[x.v] = max(mxa[x.v], mxa[x.u]);
        mxb[x.v] = max(mxb[x.v], mxb[x.u]);
    }
}

inline void undo(){
    while(tim){
    //  printf("undo %d %d\n", opt[tim].u, opt[tim].v);
        fa[opt[tim].u] = opt[tim].fa;
        mxa[opt[tim].v] = opt[tim].a;
        mxb[opt[tim].v] = opt[tim].b;
        size[opt[tim].v] = opt[tim].size;
        --tim;
    }
}

int main(){
    scanf("%d%d", &n, &m);
    blsize = sqrt(15 * m);//
    for(int i = 1; i <= m; ++i){
        scanf("%d%d%d%d", &e[i].u, &e[i].v, &e[i].a, &e[i].b);
    }
    sort(e + 1, e + m + 1, rulea);
    scanf("%d", &qsize);
    for(int i = 1; i <= qsize; ++i){
        scanf("%d%d%d%d", &q[i].u, &q[i].v, &q[i].a, &q[i].b);
        q[i].id = i;
    }
    sort(q + 1, q + qsize + 1, ruleb);
     
//  for(int i = 1; i <= m; ++i) 
//      printf("%d %d %d %d\n", e[i].u, e[i].v, e[i].a, e[i].b);
     
    for(int i = 1, lim; i <= m; i += blsize){
        bcnt = 0;
        lim = min(m, i + blsize - 1);
        for(int j = 1; j <= qsize; ++j)
            if(e[i].a <= q[j].a 
            && (i + blsize > m || e[i + blsize].a > q[j].a))
                b[++bcnt] = j;
        sort(e + 1, e + i, ruleb);//这里排的是前面整块的点! 
        for(int j = 1; j <= n; ++j){
            fa[j] = j, size[j] = 1, mxa[j] = mxb[j] = -1;
        } 
        for(int j = 1, top = 1; j <= bcnt; ++j){
            while(top < i && q[b[j]].b >= e[top].b){
                //printf("mer %d 0\n");
                merge(e[top], 0);
                ++top;
            }
            for(int k = i; k <= lim; ++k){
                if(q[b[j]].a >= e[k].a && q[b[j]].b >= e[k].b){
                    merge(e[k], 1);
                }
            }
            
            int x = find(q[b[j]].u), y = find(q[b[j]].v);
            //printf("%d %d %d %d %d %d %d\n", q[b[j]].u, q[b[j]].v, q[b[j]].id, mxa[x], mxb[x], x, y);
            ans[q[b[j]].id] = ((x == y) && (mxa[x] == q[b[j]].a) && (mxb[x] == q[b[j]].b));
            
            undo();
        }
    }
    for(int i = 1; i <= qsize; ++i)
        if(ans[i]) printf("Yes\n");
        else printf("No\n");
    return 0;
}  

转载于:https://www.cnblogs.com/hjmmm/p/10452713.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值