[ HNOI2016 ] 最小公倍数

题目

Luogu
LOJ
Acinwg

思路

[ HNOI2016 ] 最小公倍数

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int N = 100010, M = 50010;
int n, m, q, L[N], R[N], ans[N];
struct EDGE { int u, v, a, b; } E[N];
bool cmp1(EDGE a, EDGE b) { return a.a < b.a; }
bool cmp2(EDGE a, EDGE b) { return a.b < b.b; }
struct QUERY { int u, v, a, b, id; } Q[N];
bool cmp3(QUERY a, QUERY b) { return a.b < b.b; }
vector<int> T[1000];
int p[N], dep[N], MA[N], MB[N], top;
int find(int x) { return x == p[x] ? x : find(p[x]); }
struct OPER { int u, v, a, b, d; } stk[N];
void merge(int u, int v, int a, int b) {
    u = find(u), v = find(v);
    if (dep[u] < dep[v]) swap(u, v);
    stk[++top] = (OPER){ u, v, MA[u], MB[u], dep[u] };
    MA[u] = max(MA[u], a), MB[u] = max(MB[u], b);
    if (u == v) return;
    p[v] = u, dep[u] = max(dep[u], dep[v] + 1);
    MA[u] = max(MA[u], MA[v]), MB[u] = max(MB[u], MB[v]);
}
void remove(int x) {
    int u = stk[x].u, v = stk[x].v;
    p[v] = v, dep[u] = stk[x].d;
    MA[u] = stk[x].a, MB[u] = stk[x].b;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++)    
        cin >> E[i].u >> E[i].v >> E[i].a >> E[i].b;
    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(E + 1, E + m + 1, cmp1);
    sort(Q + 1, Q + q + 1, cmp3);
    int B = sqrt(m * log2(n)), S = (m + B - 1) / B;
    for (int i = 1; i <= S; i++)
        L[i] = (i - 1) * B + 1, R[i] = min(m, i * B);
    for (int i = 1, j; j = 0, i <= q; i++) {
        while (j < S && Q[i].a >= E[R[j]].a) j++;
        T[j].push_back(i);
    }
    for (int k = 1; k <= S; k++) {
        // 排序外加初始化
        sort(E + 1, E + R[k - 1] + 1, cmp2);
        for (int i = 1; i <= n; i++)
            p[i] = i, dep[i] = 0, MA[i] = MB[i] = -1;
        for (int i = 0, j = 1; i < T[k].size(); i++) {
            QUERY t = Q[T[k][i]];
            // 找一下之前的块是否可以加边
            while (j < L[k] && E[j].b <= t.b) 
                merge(E[j].u, E[j].v, E[j].a, E[j].b), j++;
            top = 0; // top重置一下, 因为之后加的边要撤销
            // 暴力找当前块, 是否有满足条件的
            for (int u = L[k]; u <= R[k]; u++)
                if (E[u].a <= t.a && E[u].b <= t.b)
                    merge(E[u].u, E[u].v, E[u].a, E[u].b);
            int x = find(t.u), y = find(t.v);
            // 如果两点联通, 且路径上最大的 A, B 符合要求
            ans[t.id] = (x == y && MA[x] == t.a && MB[x] == t.b);
            // 删除当前块加的边
            while (top) remove(top--);
        }
    }
    for (int i = 1; i <= q; i++) 
        cout << (ans[i] ? "Yes" : "No") << endl;
    return 0;
}
上一篇:二叉树的广度遍历和深度遍历


下一篇:[造个小*·数串互转函数] stoi和to_string函数实现