文章目录

‹
›

T3 涂鸦带师 题解

T3 涂鸦带师 题解

倍增很重要,经常想不到

题意

  • 圆上周长为 \(C\),第 \(i\) 张纸必须从 \(X_i\) 开始覆盖长度 \(L_i\),即覆盖圆上的弧 \([X_i,\ X_i+L_i] \pmod C\)。
  • 求最少用几张纸覆盖整个圆。
  • 若某张纸满足 \(L_i \ge C\),一张就能覆盖整个圆,答案直接是 \(1\)。

贪心思路

  • 断环成链:枚举第一张纸作为起点,把圆从 \(X_i\) 处切开拉直成 \([X_i,\ X_i+C]\)。
  • 每张纸在数轴上出现两次:\([X_i,\ X_i+L_i]\) 和 \([X_i+C,\ X_i+C+L_i]\),排序后转化为普通线段覆盖。
  • 贪心策略:当前已覆盖到 \(cur\),下一步从所有左端点 \(\le cur\) 的区间中,选择右端点最远的那个。
  • 正确性:调整法: 设最优解第一步选区间 \(A\),右端点为 \(r_A\);贪心选区间 \(B\),右端点为 \(r_B \ge r_A\)。贪心之后能接上的区间集合包含最优解之后的集合,因此把最优解第一步替换成贪心选择不会变差,归纳即证贪心最优。
  • 暴力瓶颈:枚举 \(n\) 个起点,每个起点内层从头扫描 \(2n\) 个区间,复杂度 \(O(n^2)\),问题在于每个起点都重复扫描。

倍增优化

  • 固定第一张纸后,贪心过程就是沿一个确定的函数 \(nxt\) 跳。从区间 \(i\) 出发贪心跳一步到哪,与起点无关,可以预处理好供所有起点复用。

  • 令\(pre[i] = [1, i]\) 中右端点最大的区间编号,\(nxt[i] =\)从区间 \(i\) 贪心跳一步到达的区间

  • 在所有满足 \(l_j \le r_i\) 的区间中,选择右端点 \(r_j\) 最大的区间。区间已按左端点排序,二分找到最后一个满足 \(l_p \le r_i\) 的位置 \(p\),再用前缀最大 \(pre[p]\) 直接得到右端点最远的区间编号。若最远右端点都不超过 \(r_i\),说明卡住,令 \(nxt[i]=i\)。

  • 构造倍增表:\(up[i][k]\) 表示从区间 \(i\) 出发贪心跳 \(2^k\) 步后到达的区间。递推为 \(up[i][k]=up[up[i][k-1]][k-1]\),从而把跳很多步压缩到 \(O(\log n)\)。

  • 退出倍增循环时,\(cur\) 一定还没到达目标,因此需要再补一步 \(nxt[cur]\)。补完后若 \(sr \ge target\),说明该起点有解。

复杂度

  • \(O(n\log n)\)。

AC code

#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using pll = pair<ll, ll>;

const int N = 2e5 + 5;
const int LOG = 20;
const ll INF = 1e18;

ll C, n, m;
ll x[N], l[N], y[N];

ll sl[N * 2], sr[N * 2];
ll pre[N * 2], nxt[N * 2];

ll up[N * 2][LOG];

int main () {
    freopen("paper.in", "r", stdin);
    freopen("paper.out", "w", stdout);

    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> C >> n;
    for (int i = 1; i <= n; i ++) cin >> x[i] >> l[i], y[i] = x[i] + l[i];

    for (int i = 1; i <= n; i ++) {
        if (l[i] >= C) {
            cout << 1 << '\n';
            return 0;
        }
    }

    vector<pll> seg;

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

    sort(seg.begin(), seg.end());
    m = 2 * n;

    for (int i = 1; i <= m; i ++) {
        sl[i] = seg[i - 1].first;
        sr[i] = seg[i - 1].second;
    }

    for (int i = 1; i <= m; i ++) {
        if (i == 1 || sr[i] > sr[pre[i - 1]]) pre[i] = i;
        else pre[i] = pre[i - 1];
    }

    for (int i = 1; i <= m; i ++) {
        int l = 1, r = m, p = 0;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (sl[mid] <= sr[i]) {
                p = mid;
                l = mid + 1;
            }
            else r = mid - 1;
        }

        int j = pre[p];
        if (sr[j] <= sr[i]) nxt[i] = i;
        else nxt[i] = j;
    }

    for (int i = 1; i <= m; i ++) up[i][0] = nxt[i];
    for (int k = 1; k < LOG; k ++) {
        for (int i = 1; i <= m; i ++) {
            up[i][k] = up[up[i][k - 1]][k - 1];
        }
    }

    ll ans = INF;

    for (int i = 1; i <= n; i ++) {
        int st = lower_bound(seg.begin(), seg.end(), make_pair(x[i], y[i])) - seg.begin() + 1;
        ll ed = x[i] + C;

        int cur = st;
        ll cnt = 1;
        bool flag = true;

        for (int k = LOG - 1; k >= 0; k --) {
            int nx = up[cur][k];

            if (nx != cur && sr[nx] < ed) {
                cur = nx;
                cnt += (1 << k);
            }
        }

        int nx = nxt[cur];
        if (nx != cur) {
            cur = nx;
            cnt ++;
        }

        if (sr[cur] < ed) flag = false;
        if (flag) ans = min(ans, cnt);
    }

    if (ans == INF) cout << "-1\n";
    else cout << ans << '\n';

    return 0;
}
posted @ 2026-10-26 15:33  lvwangshu  阅读(34)  评论(0)    收藏  举报