HNOI 2016 最小公倍数

最小公倍数

解读题意

判定有无路径包含u,v,且路径上的max{a[]}=A ,max{b[]}=B

注意

最大值的初值不能设成0,必须小于0。

切分

P20暴力:O(q,m)
并查集里存par,mxa,mxb
对于每个询问 q
每次清空并查集
把小于等于a且小于等于b的边并起来(边指到点上)
观察u,v是否在一个集合里
观察集合mxa是否等于a
观察集合mxb是否等于b
如果同时满足则存在这种路径

P40链:O(q*log(n)^2)
建一颗线段树,能够查询区间最大值(maxA,maxB)
对于每个询问q
先查找区间[u,v]最大值是否小于等于A,B
在向左右查找能否包含A,B(二分查找+区间查询)

考虑离线(可以让a,b有序)
P a=0: 并查集均摊O(n+m)+排序O(q+qlogq)
并查集存par和mx(并查集中最大的b)
由于a一直等于0,那么按b排序(从小到大)
不清空并查集
把小于等于b的边并起来(边随点并)
查询u,v是否在一个集合里
查询这个集合的mx是否等于b

pa,b<=30:
离线处理,按b从小到大排序。
开31个并查集
不清空并查集
把b<=B的边加入a<=A的每个并查集(每次加边更新最多31个并查集)
在[A]并查集里查询u,v是否在一个集合里
在[A]并查集查询这个集合的mxa,mxb是否等于A,B

正解流程

1.离线处理
2.分块+并查集+并查集回溯+并查集归并
3.把边按a排序,分成 m m
4.把询问按a排序,依次放进对应的(边)所属的块中,询问优先属于后面的块
5.把每个块中的边和询问按b排序
6.对每块依次处理
7.处理当前块时,把前面块放到一起按b排序
8.对当前块中的询问按b依次回答,先把符合b<=B的边加入并查集(不包含当前块)。
9.把前面块的边加入后,把当前块中符合a<=A&&b<=B的边加入并查集(这时要用栈记录并查集的更新,完成询问后撤销)
10.只记录和撤销当前块中的边的加入

总复杂度:排序(qlogq+mlogm)+并查集的加入和撤销(q* m m )+所有边的加入(m* m m )

具体代码

#include<bits/stdc++.h>
using namespace std;
const int M=50005;
//无向图,边有权值(a,b)
//多询问:判定有无:路径max{a[]}=a ,max{b[]}=b
int n,m,q,S,top;
bool ans[M];
struct Query {
    int u,v,a,b,id;
} Qu[M],way[M*2],Qy;
vector<Query>Q[M];
bool cmp1(Query a,Query b) {
    return a.a<b.a;
}
bool cmp2(Query a,Query b) {
    return a.b<b.b;
}
struct node {
    int t1,fa,t2,mxa,mxb,dep;
    void rd(int a,int b,int c,int d,int e,int f) {
        t1=a,fa=b,t2=c,mxa=d,mxb=e,dep=f;
    }
} stk[M*2];
int fa[M],dep[M],mxa[M],mxb[M];
void init() {
    top=0;
    for(int i=1; i<=n; i++) {
        fa[i]=i;
        dep[i]=0;
        mxa[i]=mxb[i]=-1;
    }
}
int get_fa(int x) {
    if(fa[x]==x)return x;
    return get_fa(fa[x]);
}
void merge(int x,int y,int a,int b) {
    int t1=get_fa(x);
    int t2=get_fa(y);
    if(dep[t1]>dep[t2])swap(t1,t2);
    stk[++top].rd(t1,fa[t1],t2,mxa[t2],mxb[t2],dep[t2]);
    fa[t1]=t2;
    mxa[t2]=max(mxa[t2],max(mxa[t1],a));
    mxb[t2]=max(mxb[t2],max(mxb[t1],b));
    if(t1!=t2&&dep[t1]==dep[t2])dep[t2]++;
}
void recover(node K) {
    int t1=K.t1,t2=K.t2;
    fa[t1]=K.fa;
    dep[t2]=K.dep;
    mxa[t2]=K.mxa;
    mxb[t2]=K.mxb;
}
int main() {
    scanf("%d %d",&n,&m);
    S=sqrt(m);
    for(int i=0; i<m; i++) {
        scanf("%d %d %d %d",&way[i].u,&way[i].v,&way[i].a,&way[i].b);
    }
    sort(way,way+m,cmp1);
    scanf("%d",&q);
    for(int i=1; i<=q; i++) {
        scanf("%d %d %d %d",&Qu[i].u,&Qu[i].v,&Qu[i].a,&Qu[i].b);
        Qu[i].id=i;
    }
    sort(Qu+1,Qu+1+q,cmp1);
    int nw=0;
    for(int i=1; i<=q; i++) {
        while(nw+1<m&&way[nw+1].a<=Qu[i].a)nw++;
        Q[nw/S].push_back(Qu[i]);
    }
    for(int i=0; i<m; i+=S) {
        sort(way+i,way+min(i+S,m),cmp2);
    }
    for(int i=0; i<m; i+=S) {
        if(Q[i/S].size()>0) {
            sort(Q[i/S].begin(),Q[i/S].end(),cmp2);
        }
    }
    for(int i=0; i<m; i+=S) {
        sort(way,way+i,cmp2);
        int nw=0;
        init();
        for(int j=0; j<Q[i/S].size(); j++) {
            Qy=Q[i/S][j];
            while(nw<i&&way[nw].b<=Qy.b) {
                merge(way[nw].u,way[nw].v,way[nw].a,way[nw].b);
                nw++;
            }
            int ntop=top;
            for(int k=i; k<m&&k<i+S; k++) {
                if(way[k].a<=Qy.a&&way[k].b<=Qy.b) {
                    merge(way[k].u,way[k].v,way[k].a,way[k].b);
                }
            }
            int x=get_fa(Qy.u),y=get_fa(Qy.v);
            if(x!=y||mxa[x]!=Qy.a||mxb[x]!=Qy.b)ans[Qy.id]=0;
            else ans[Qy.id]=1;
            while(top>ntop)recover(stk[top--]);
        }
    }
    for(int i=1; i<=q; i++)printf("%s\n",ans[i]?"Yes":"No");
    return 0;
}
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值