跳转至

251117 模拟赛

T1

对于一个弱连通块,如果存在环,那么答案是 \(sz\),否则是 \(sz-1\)。记得判掉自环。

T2

作狄利克雷后缀和,然后简单用 bitset 维护即可。

T3

\(n\) 座工厂,第 \(i\) 座工厂可以用 \(a_i\) 个金币买下,之后每时刻可以产生 \(b_i\) 枚金币,每时刻你只能选择一座工厂产出金币。保证存在一个 \(i\) 满足 \(a_i=0\)。问最少用多久拿到 \(m\) 个金币。

\(n\le 10^6,~m\le 10^{16},~0\le a_i,b_i\le 10^8\)

选择的工厂的 \(a,b\) 肯定都是单调递增的。如果存在 \(i,j\) 满足 \(a_i\le a_j,~b_i\ge b_j\),那么可以删去 \(j\)。这样剩下的 \(a,b\) 都是单调递增的了。

\(f_i\) 表示买下第 \(i\) 个工厂的最早时间,\(g_i\) 表示买下后最多剩下多少钱。能够说明以时间为第一关键字,剩下的钱为第二关键字进行比较是正确的。进行连续段 dp,可以做到 \(O(n^2)\),转移式如下:

\[ \Big(f_j+T(j,a_i),~g_j+T(j,a_i)b_j-a_i\Big)\to (f_i,g_i) \]

其中 \(T(j,x)\) 表示通过 \(g_j\)\(b_j\) 计算出金币数达到 \(x\) 的时间。\(T(j,x)=\lceil\frac{x-g_j}{b_j}\rceil\)

考虑斜率优化,记录 \(f_i\) 表示工厂 \(i\) 对应的直线的截距的最大值。由于 \(a_i\) 是单调递增的,因此一定不会出现在买下 \(i\) 工厂之前用 \(i\) 进行转移的情况。用单调队列维护 \(y=f_i+b_ix\) 形成的凸包,转移时需要找到 \(y=a_i\) 与凸包的交点。考虑 hd+1 是否比 hd 更优,若是则弹出。这里需要双关键字比较,可以自己画图想一想。压入 \(i\) 时通过计算直线的交点来决定是否弹出队尾。

代码
#include<iostream>
#include<algorithm>
#include<cassert>
#define int long long
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f3f3f3f3f;

struct Pt {
    int a, b;
    inline bool operator<(const Pt &oth) const {
        return a < oth.a;
    }
} p[N];

int n, m;
int a[N], b[N];

int que[N], hd, tl;
int f[N], ans = INF;

int t(int x, int id) {
    if(b[id] == 0) return x == 0 ? 0 : INF;
    return (x - f[id] + b[id] - 1) / b[id];
}

bool cmp(int x, int y, int z) {
    int t1 = t(z, x);
    int t2 = t(z, y);
    if(t1 < t2) return true;
    else if(t1 == t2 && f[x] + t1 * b[x] > f[y] + t2 * b[y]) return true;
    return false;
}

signed main() {

    freopen("c.in", "r", stdin);
    freopen("c.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> p[i].a >> p[i].b;
    sort(p + 1, p + 1 + n);

    for(int i = 1; i <= n; i++) a[i] = p[i].a;
    for(int i = 1; i <= n; i++) b[i] = p[i].b;

    hd = 1, tl = 0;
    que[++tl] = 0;
    for(int i = 1, mx = 0; i <= n; i++) {
        if(b[i] <= mx) continue;
        mx = b[i];
        while(hd < tl && cmp(que[hd + 1], que[hd], a[i])) ++hd;
        f[i] = f[que[hd]] + t(a[i], que[hd]) * (b[que[hd]] - b[i]) - a[i];
        while(hd < tl && (f[que[tl - 1]] - f[que[tl]]) * (b[i] - b[que[tl]]) >= (b[que[tl]] - b[que[tl - 1]]) * (f[que[tl]] - f[i])) --tl; que[++tl] = i;
        ans = min(ans, t(m, i));
    }
    cout << ans << '\n';

    return 0;
}

T4

对于一个排列 \(p_{1\sim n}\),考虑数组 \(a_i=\sum_{j\le i}[p_j\ge p_i]\)。可以证明在 \(\forall i,0\le a_i<i\) 时,可以通过 \(a\) 唯一确定一个 \(p\)

你需要支持两种操作:

  • 给定 \(x,y\),执行 \(a_x\gets y\)
  • 给定 \(x\),求通过 \(a\) 推出的 \(p\) 的第 \(x\) 项是多少;

\(n,q\le 10^5,~0\le y<x\)

考虑一种暴力:维护一个变量 \(res\) 初始为 \(a_x-1\),然后 \(i\) 依次遍历 \(x+1\to n\),执行 \(res\gets res+[a_i\le res]\)。正确性显然,考虑优化这个过程。

对于一段区间,输出的 \(res\) 随输入的 \(res\) 单调不降,并且本质不同的差值只有区间长度个。对序列进行分块,对每块维护 \(B\) 个分界点,查询时散块暴力,整块对 \(B\) 个分界点进行二分即可,单次查询 \(O(\frac{n}{B}\log n)\)。考虑如何处理修改,注意到可以以区间长度同阶的时间复杂度归并两个块的分界点。因此对每个块维护一棵线段树,修改时一路 push_up 上去,总共 \(O(B)\)。平衡得时间复杂度 \(O(n\sqrt{n\log n})\)

代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e5 + 10;
int B;

int n, m;
int a[N];

int blo[N], rt[N];

namespace SegT {
    struct Node {
        int lc, rc;
        vector<int> c;
    } tr[4 * N];
    int nn;
    inline int &lc(int x) { return tr[x].lc; }
    inline int &rc(int x) { return tr[x].rc; }
    inline void push_up(int p, int l, int r, int mid) {
        for(int i = 0, j = 0, k = 0; i <= mid - l + 1; i++) {
            while(j <= r - mid && tr[rc(p)].c[j] <= tr[lc(p)].c[i + 1] - 1 + i) {
                int tmp = max(tr[lc(p)].c[i], tr[rc(p)].c[j] - i);
                while(k <= i + j) tr[p].c[k++] = tmp;
                if(tr[rc(p)].c[j + 1] <= tr[lc(p)].c[i + 1] + i) j++;
                else break;
            }
        }
    }
    void build(int &p, int l, int r, int a[]) {
        if(!p) {
            p = ++nn;
            tr[p].c.resize(r - l + 3);
            tr[p].c[r - l + 2] = N + 5;
        }
        if(l == r) {
            tr[p].c[0] = 0;
            tr[p].c[1] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lc(p), l, mid, a);
        build(rc(p), mid + 1, r, a);
        push_up(p, l, r, mid);
    }
    void update(int p, int l, int r, int q, int v) {
        if(l == r) return tr[p].c[1] = v, void();
        int mid = (l + r) >> 1;
        if(q <= mid) update(lc(p), l, mid, q, v);
        else update(rc(p), mid + 1, r, q, v);
        push_up(p, l, r, mid);
    }
    int query(int p, int x) {
        return upper_bound(tr[p].c.begin(), tr[p].c.end(), x) - 1 - tr[p].c.begin();
    }
}

int main() {

    freopen("d.in", "r", stdin);
    freopen("d.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i], a[i] = i - a[i] - 1;

    B = sqrt(n * __lg(n) + 5);
    for(int i = 1; i <= n; i++) blo[i] = (i + B - 1) / B;
    for(int i = 1; i <= n; i += B) {
        SegT::build(rt[blo[i]], 1, min(n - i + 1, B), a + i - 1);
    }

    while(m--) {
        int op, x, y;
        cin >> op >> x;
        if(op == 1) {
            cin >> y; y = x - y - 1;
            a[x] = y;
            SegT::update(rt[blo[x]], 1, min(n - (blo[x] - 1) * B, B), x - (blo[x] - 1) * B, y);
        } else {
            int res = a[x]; x++;
            while(x <= n && x % B != 1) res += a[x++] <= res;
            while(x <= n) res += SegT::query(rt[blo[x]], res), x += B;
            cout << res + 1 << '\n';
        }
    }

    return 0;
}