Codeforces 901C Bipartite Segments

题目蓝链

Solution

因为这是一个仙人掌图,所以我们可以通过\(tarjan\)缩点双找出图中所有的简单环。然后我们找出每一个环中的最大编号和最小编号,并按最大编号从小到大排序。然后我们把询问离线下来,并且挂在询问的右端点处

然后我们从左往右扫,处理每一个节点处的询问之前,先把最左能走的不会形成环的位置更新出来。然后用线段树把这一段区间加\(1\)

询问就直接再线段树上区间求和就行了

还有一种做法,就是直接预处理出每一个点最左能走的不会形成环的位置,记为\(pos[i]\)。对于查询\([l, r]\),二分找到最小的\(i\)使得\(pos[i] > l\)。然后我们分别对\([l, i - 1]\)\([i, r]\)计算贡献,加起来就可以了

Code

#include <bits/stdc++.h>

using namespace std;

#define fst first
#define snd second
#define squ(x) ((LL)(x) * (x))
#define debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef pair<int, int> pii;

inline int read() {
    int sum = 0, fg = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1;
    for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
    return fg * sum;
}

const int maxn = 3e5 + 10;

vector<int> g[maxn];
vector<pii> v[maxn];

int d[maxn], low[maxn], pre[maxn], dfn;
int S[maxn], top, cnt, n, m, q;

pii p[maxn];
LL ans[maxn];

void tarjan(int now, int f) {
    d[now] = low[now] = ++dfn;
    S[++top] = now;
    for (int i = 0; i < g[now].size(); i++) {
        int son = g[now][i];
        if (son == f) continue;
        if (!d[son]) {
            tarjan(son, now);
            low[now] = min(low[now], low[son]);
            if (low[son] >= d[now]) {
                int Max = 0, Min = n + 1, tot = 0;
                while (top) {
                    int x = S[top--];
                    Max = max(Max, x);
                    Min = min(Min, x);
                    ++tot;
                    if (x == son) break;
                }
                Max = max(Max, now), Min = min(Min, now);
                if (tot > 1) p[++cnt] = (pii){Max, Min};
            }
        }else low[now] = min(low[now], d[son]);
    }
}

namespace ST {

    LL A[maxn << 2], tag[maxn << 2];
#define ls (rt << 1)
#define rs (rt << 1 | 1)

    void push_up(int rt) { A[rt] = A[ls] + A[rs]; }

    void push_down(int rt, int l, int r) {
        if (!tag[rt]) return;
        int mid = (l + r) >> 1;
        tag[ls] += tag[rt];
        tag[rs] += tag[rt];
        A[ls] += tag[rt] * (mid - l + 1);
        A[rs] += tag[rt] * (r - mid);
        tag[rt] = 0;
    }

    void change(int rt, int l, int r, int L, int R) {
        if (L <= l && r <= R) {
            A[rt] += r - l + 1, ++tag[rt]; return;
        }
        push_down(rt, l, r);
        int mid = (l + r) >> 1;
        if (L <= mid) change(ls, l, mid, L, R);
        if (R > mid) change(rs, mid + 1, r, L, R);
        push_up(rt);
    }

    LL query(int rt, int l, int r, int L, int R) {
        if (L <= l && r <= R) return A[rt];
        push_down(rt, l, r);
        int mid = (l + r) >> 1; LL res = 0;
        if (L <= mid) res += query(ls, l, mid, L, R);
        if (R > mid) res += query(rs, mid + 1, r, L, R);
        return res;
    }

}

int main() {
#ifdef xunzhen
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);
#endif

    n = read(), m = read();
    for (int i = 1; i <= m; i++) {
        int x = read(), y = read();
        g[x].push_back(y);
        g[y].push_back(x);
    }

    q = read();
    for (int i = 1; i <= q; i++) {
        int x = read(), y = read();
        v[y].push_back((pii){x, i});
    }

    for (int i = 1; i <= n; i++) if (!d[i]) tarjan(i, 0);

    sort(p + 1, p + cnt + 1);
    int j = 1, Max = 0;
    for (int i = 1; i <= n; i++) {
        while (j <= cnt && p[j].fst <= i) Max = max(Max, p[j++].snd);
        ST::change(1, 1, n, Max + 1, i);
        for (int j = 0; j < v[i].size(); j++)
            ans[v[i][j].snd] = ST::query(1, 1, n, v[i][j].fst, i);
    }

    for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);

    return 0;
}

Summary

考试的时候没有注意到这是一个仙人掌,以为这是一个仙人球,然后我就打了一个缩边双...

转载于:https://www.cnblogs.com/xunzhen/p/9811130.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值