4537: [Hnoi2016]最小公倍数

4537: [Hnoi2016]最小公倍数

Time Limit: 40 Sec   Memory Limit: 512 MB
Submit: 688   Solved: 271
[ Submit][ Status][ Discuss]

Description

  给定一张N个顶点M条边的无向图(顶点编号为1,2,…,n),每条边上带有权值。所有权值都可以分解成2^a*3^b
的形式。现在有q个询问,每次询问给定四个参数u、v、a和b,请你求出是否存在一条顶点u到v之间的路径,使得
路径依次经过的边上的权值的最小公倍数为2^a*3^b。注意:路径可以不是简单路径。下面是一些可能有用的定义
:最小公倍数:K个数a1,a2,…,ak的最小公倍数是能被每个ai整除的最小正整数。路径:路径P:P1,P2,…,Pk是顶
点序列,满足对于任意1<=i<k,节点Pi和Pi+1之间都有边相连。简单路径:如果路径P:P1,P2,…,Pk中,对于任意1
<=s≠t<=k都有Ps≠Pt,那么称路径为简单路径。

Input

  输入文件的第一行包含两个整数N和M,分别代表图的顶点数和边数。接下来M行,每行包含四个整数u、v、a、
b代表一条顶点u和v之间、权值为2^a*3^b的边。接下来一行包含一个整数q,代表询问数。接下来q行,每行包含四
个整数u、v、a和b,代表一次询问。询问内容请参见问题描述。1<=n,q<=50000、1<=m<=100000、0<=a,b<=10^9

Output

  对于每次询问,如果存在满足条件的路径,则输出一行Yes,否则输出一行 No(注意:第一个字母大写,其余
字母小写) 。

Sample Input

4 5
1 2 1 3
1 3 1 2
1 4 2 1
2 4 3 2
3 4 2 2
5
1 4 3 3
4 2 2 3
1 3 2 2
2 3 2 2
1 3 4 4

Sample Output

Yes
Yes
Yes
No
No

HINT

Source

[ Submit][ Status][ Discuss]

最小公倍数的话,因为2和3互质,所以影响是分开的
从x到y找一条最小公倍数是2^a*3^b的路径
这就意味着原图当中所有边权带有2^k(k > a)的边都不能走,同理3的那部分
不但不能走大边,还一定得经过至少一条刚刚好有k = a的边才行
如果我们排除了所有大边的影响,小边显然怎么走都无所谓,那干脆都走一遍好了
也就是说我们要维护这个图的连通性,一个连通块内能走到的a,b的最大值
这显然是交给并查集
但是怎么让所有询问处理的时候保证并查集内只有需要的边。。
先把所有边按照a为关键字排序好,然后对整个边集分块,就是每siz条边分一个块
然后把所有询问按照a对应放在块内
处理每个块内的询问的时候,先把之前所有块的边按照b为关键字排好,当前询问也按照b的关键字排好
对于前面的边,就可以按照b的顺序慢慢加入图中,对于当前处理的询问,当前块内的边暴力看看谁该进图
这样就必须维护一个可删边的并查集,,并查集按轶合并就行了


一开始写的时候。。并查集合并忘记把子树信息交给父亲了。。。
然后,块的大小取根号mlogm,因为多次sort的常数巨大。。减少一些sort的次数对程序改善良好
#include<iostream>  
#include<vector>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<stack>  
#include<cmath>
using namespace std;  
  
const int maxn = 5E4 + 50;  
const int maxm = 1E5 + 10;  
  
struct E{  
    int x,y,a,b; E(){}  
    E(int x,int y,int a,int b): x(x),y(y),a(a),b(b){}  
}edgs[maxm];  
bool Ecmpa(const E &A,const E &B) {return A.a < B.a;}  
bool Ecmpb(const E &A,const E &B) {return A.b < B.b;}  
  
struct Q{  
    int x,y,a,b,num; Q(){}  
    Q(int x,int y,int a,int b,int num): x(x),y(y),a(a),b(b),num(num){}  
}qu[maxn];  
bool Qcmpa(const Q &A,const Q &B) {return A.a < B.a;}  
bool Qcmpb(const Q &A,const Q &B) {return A.b < B.b;}  
  
struct G{  
    int typ,x,y,a,b,L; G(){}  
    G(int typ,int x,int y,int a,int b,int L): typ(typ),x(x),y(y),a(a),b(b),L(L){}  
}s[maxm];  
  
int n,q,m,cnt,top,Sqrt,fa[maxn],D[maxn],Maxa[maxn],Maxb[maxn],L[maxn],R[maxn];  
bool Ans[maxn];  
  
vector <Q> Qu[500];  
  
int getint()  
{  
    char ch = getchar();  
    int ret = 0;  
    while (ch < '0' || '9' < ch) ch = getchar();  
    while ('0' <= ch && ch <= '9')  
        ret = ret*10 + ch - '0',ch = getchar();  
    return ret;  
}  
  
void Pre_Work()  
{  
    for (int i = 1; i <= m; i++) {  
        int x,y,a,b;  
        x = getint(); y = getint();  
        a = getint(); b = getint();  
        edgs[i] = E(x,y,a,b);  
    }  
    sort(edgs + 1,edgs + m + 1,Ecmpa);  
    for (int l = 1,r = Sqrt; l <= m; l += Sqrt,r += Sqrt) {  
        r = min(r,m); ++cnt;  
        L[cnt] = l; R[cnt] = r;  
    }  
    q = getint();  
    for (int i = 1; i <= q; i++) {  
        int x,y,a,b;  
        x = getint(); y = getint();  
        a = getint(); b = getint();  
        qu[i] = Q(x,y,a,b,i);  
    }  
    sort(qu + 1,qu + q + 1,Qcmpb);
    for (int i = 1; i <= q; i++)  
        for (int j = cnt; j; j--)   
            if (edgs[L[j]].a <= qu[i].a && qu[i].a <= edgs[R[j]].a) {  
                Qu[j].push_back(qu[i]);  
                break;  
            }  
    for (int i = 1; i <= n; i++) fa[i] = i,D[i] = 1,Maxa[i] = Maxb[i] = -1;  
}  
  
int getfa(int x) {return x == fa[x]?x:getfa(fa[x]);}  
void Insert(E e)  
{  
    int fx = getfa(e.x),fy = getfa(e.y);  
    if (fx == fy) {  
        s[++top] = G(0,fx,fx,Maxa[fx],Maxb[fx],D[fx]);  
        Maxa[fx] = max(Maxa[fx],e.a);  
        Maxb[fx] = max(Maxb[fx],e.b);  
    }  
    else {  
        if (D[fx] >= D[fy]) swap(fx,fy);  
        s[++top] = G(1,fx,fy,Maxa[fy],Maxb[fy],D[fy]);  
        fa[fx] = fy;  
        Maxa[fy] = max(Maxa[fy],max(Maxa[fx],e.a));  
        Maxb[fy] = max(Maxb[fy],max(Maxb[fx],e.b));  
        D[fy] = D[fx] == D[fy]?D[fy]+1:D[fy];  
    }  
}  
  
void Del(int goal)  
{  
    while (top > goal) {  
        G t = s[top--];  
        if (t.typ == 0) Maxa[t.x] = t.a,Maxb[t.x] = t.b;  
        else {  
            fa[t.x] = t.x; D[t.y] = t.L;  
            Maxa[t.y] = t.a,Maxb[t.y] = t.b;  
        }  
    }  
}  
  
int main()  
{  
    #ifdef DMC  
        freopen("DMC.txt","r",stdin);  
    #endif  
      
    n = getint(); m = getint(); Sqrt = sqrt(m*log2(m));  
    int tot = 0; Pre_Work(); 
    for (int i = 1; i <= cnt; i++) {  
        //sort(Qu[i].begin(),Qu[i].end(),Qcmpb);  
        int now = 1; top = 0;  
        for (int j = 0; j < Qu[i].size(); j++) {  
            Q t = Qu[i][j];  
            while (now <= tot && edgs[now].b <= t.b) Insert(edgs[now++]);  
            int g = top;  
            for (int k = L[i]; k <= R[i]; k++)   
                if (edgs[k].a <= t.a) {  
                    if (edgs[k].b <= t.b) Insert(edgs[k]);  
                }  
                else break;  
            int fx = getfa(t.x),fy = getfa(t.y);  
            if (fx == fy && Maxa[fx] == t.a && Maxb[fx] == t.b)   
                Ans[t.num] = 1;  
            else Ans[t.num] = 0;  
            Del(g);  
        }  
        sort(edgs + 1,edgs + R[i] + 1,Ecmpb);   
        tot = R[i]; Del(0);  
    }  
    for (int i = 1; i <= q; i++)   
        if (!Ans[i]) puts("No"); else puts("Yes");  
    return 0;  
}  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值