《信息学奥赛一本通·高手专项训练》集训 Day 7

线段树

100+50+100=250/Rank 3\color{Green}100\color{Black}+\color{Orange}50\color{Black}+\color{Green}100\color{Black}=\color{#92E411}250\color{Black}/\color{Orange}\text{Rank 3}

A. 筹备计划\color{#9D3DCF}\text{A. 筹备计划}

题目

同学们现在分散在数轴上 1n1\sim n 的整点上,设整点 ii 上的同学数量为 aia_i

还有一个集合位置的候选集合 SS,初始为 {1,2,,n}\{1,2,…,n\}

你还要处理 qq 次操作,每次操作,可能发生以下事件:

  • 在某个位置的同学增加 xx 个或减少 xx 个。
  • 令某个区间 [x,y][x,y] 内的整点成为可以选择的整点(将 [x,y][x,y] 中不在 SS 内的点加入 SS)。
  • 令某个区间 [x,y][x,y] 内的整点成为不能选择的整点(将 SS 中在 [x,y][x,y] 内的点删去)。

对于区间限制的操作,不保证操作前该区间的整点是否能选择。操作的影响都是会保留下来的。

每次操作过后,你都需要找到 中的一个整点 作为集合位置,最小化所有同学走到该点的距离总和。形式化地,你需要求

minkS{i=1nai×ik}\min_{k\in S}\{\sum_{i=1}^na_i\times|i-k|\}

如果有多个满足条件的 kk,选择 kk 最小的那个。

题解

如果没有关于 SS 的限制,那么这就是个小学数学题,如果把 aia_iii 从小到大排成一列,那么 kk 就是中位数。

换句话说,kk 就是满足 i=1k1ai<i=1nai2i=1kai\sum_{i=1}^{k-1}a_i<\frac{\sum_{i=1}^na_i}{2}\le\sum_{i=1}^ka_ikk(此为 i=1nai\sum_{i=1}^na_i 为奇数的情况,仅作为例子)。

但是加上限制后,kk 就不一定能取到了,但是能符合要求的 kk 仍然是在 kk 左右最靠近 kk 的可选整点之一,我们可以在线段树上二分来寻找,以左边为例,如果当前区间 [l,r][l,r] 的右半边 [mid+1,min(r,k)][mid+1,\min(r,k)] 存在可选的整点,就递归右区间,否则递归左区间。

找到后,把绝对值拆成两部分计算答案,取答案较小的那个即可。

具体来说,我们需要用一个线段树维护以下信息:

  • 区间整点的个数和 zsumzsum
  • 区间 aia_i 的和 sumsum
  • 区间 ai×ia_i\times i 的和 mulmul
  • 后面两个操作的区间标记 tagtag

时间复杂度 O(nlogn)O(n\log n)

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 2e5 + 10;
int n, q;
ll a[N];
#define pl p << 1

#define pr p << 1 | 1

#define S_T_T ll

struct Segment_Tree {
    struct Tree {
        int l, r;
        int tag;
        S_T_T zsum, sum, mul;
    } a[N * 4];
    void pushup(int p) {
        a[p].zsum = a[pl].zsum + a[pr].zsum;
        a[p].sum = a[pl].sum + a[pr].sum;
        a[p].mul = a[pl].mul + a[pr].mul;
    }
    void pushdown(int p) {
        if (a[p].tag != -1) {
            a[pl].zsum = a[p].tag * (a[pl].r - a[pl].l + 1);
            a[pr].zsum = a[p].tag * (a[pr].r - a[pr].l + 1);
            a[pl].tag = a[pr].tag = a[p].tag;
            a[p].tag = -1;
        }
    }
    void build(int p, int l, int r) {
        a[p].l = l;
        a[p].r = r;
        a[p].tag = -1;
        if (l == r) {
            a[p].zsum = a[p].sum = a[p].mul = 0;
            return;
        }
        int mid = (l + r) >> 1;
        build(pl, l, mid);
        build(pr, mid + 1, r);
        pushup(p);
    }
    void change1(int p, int l, int r, S_T_T v) {
        if (l <= a[p].l && a[p].r <= r) {
            a[p].tag = v;
            a[p].zsum = v * (a[p].r - a[p].l + 1);
            return;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        if (l <= mid)
            change1(pl, l, r, v);
        if (r > mid)
            change1(pr, l, r, v);
        pushup(p);
    }
    void change2(int p, int x, S_T_T v) {
        if (a[p].l == a[p].r) {
            a[p].sum += v;
            a[p].mul += v * x;
            return;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        if (x <= mid)
            change2(pl, x, v);
        if (x > mid)
            change2(pr, x, v);
        pushup(p);
    }
    S_T_T askzsum(int p, int l, int r) {
        if (l <= a[p].l && a[p].r <= r) {
            return a[p].zsum;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        S_T_T ans = 0;
        if (l <= mid)
            ans += askzsum(pl, l, r);
        if (r > mid)
            ans += askzsum(pr, l, r);
        return ans;
    }
    S_T_T asksum(int p, int l, int r) {
        if (l <= a[p].l && a[p].r <= r) {
            return a[p].sum;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        S_T_T ans = 0;
        if (l <= mid)
            ans += asksum(pl, l, r);
        if (r > mid)
            ans += asksum(pr, l, r);
        return ans;
    }
    S_T_T askmul(int p, int l, int r) {
        if (l > r)
            return 0;
        if (l <= a[p].l && a[p].r <= r) {
            return a[p].mul;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        S_T_T ans = 0;
        if (l <= mid)
            ans += askmul(pl, l, r);
        if (r > mid)
            ans += askmul(pr, l, r);
        return ans;
    }
    ll query_k(int p, ll ksum) {
        if (a[p].l == a[p].r) {
            return a[p].l;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        if (ksum <= a[pl].sum)
            return query_k(pl, ksum);
        else
            return query_k(pr, ksum - a[pl].sum);
    }
    ll query_l(int p, ll kz) {
        if (!kz)
            return 0;
        if (a[p].l == a[p].r) {
            return a[p].l;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        if (kz <= a[pl].zsum)
            return query_l(pl, kz);
        else
            return query_l(pr, kz - a[pl].zsum);
    }
    ll query_r(int p, ll kz) {
        if (!kz)
            return 0;
        if (a[p].l == a[p].r) {
            return a[p].l;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        if (kz <= a[pr].zsum)
            return query_r(pr, kz);
        else
            return query_r(pl, kz - a[pr].zsum);
    }
} tree;
int main() {
    freopen("position.in", "r", stdin);
    freopen("position.out", "w", stdout);
    n = read();
    q = read();
    tree.build(1, 1, n);
    tree.change1(1, 1, n, 1);
    for (int i = 1; i <= n; i++) {
        tree.change2(1, i, read());
    }
    while (q--) {
        ll type, x, y;
        type = read();
        x = read();
        y = read();
        if (type == 1)
            tree.change2(1, x, y);
        else if (type == 2)
            tree.change2(1, x, -y);
        else if (type == 3)
            tree.change1(1, x, y, 1);
        else if (type == 4)
            tree.change1(1, x, y, 0);
        ll sum = tree.a[1].sum, ksum, zk, k;
        ksum = (sum + 1) / 2;
        k = tree.query_k(1, ksum);
        zk = tree.askzsum(1, 1, k);
        ll L = tree.query_l(1, zk), R = tree.query_r(1, tree.a[1].zsum - zk);
        if (!L && !R) {
            puts("-1");
            continue;
        }
        if ((!L && R) || (!R && L)) {
            write(L + R);
            putchar('\n');
            continue;
        }
        ll vl = tree.asksum(1, 1, L) * L - tree.askmul(1, 1, L) + tree.askmul(1, L + 1, n) -
                tree.asksum(1, L + 1, n) * L;
        ll vr = tree.asksum(1, 1, R) * R - tree.askmul(1, 1, R) + tree.askmul(1, R + 1, n) -
                tree.asksum(1, R + 1, n) * R;
        if (vl <= vr)
            write(L);
        else
            write(R);
        putchar('\n');
    }
    return 0;
}

B. 求公约数\color{#9D3DCF}\text{B. 求公约数}

题目

小翔有一个长度为 nn排列 aa。定义一个区间 的价值为:

value(l,r)=maxli<jr{gcd(ai,aj)}\text{value}(l,r)=\max_{l\le i<j\le r}\{\gcd(a_i,a_j)\}

其中 gcd(i,j)\gcd(i,j) 表示整数 i,ji,j 的最大公约数。

现在,小翔给了你一个任务:对于每个正整数 (1xn)(1\le x\le n),你需要计算出有多少对 l,r(1l<rn)l,r(1\le l< r\le n),满足 value(l,r)=x\text{value}(l,r)=x

题解

我们可以从大到小枚举 x=n1x=n\rightarrow 1,注意到若 value(l,r)=x\text{value}(l,r)=x,则 [l,r][l,r] 中至少会出现两个 xx 的倍数,于是我们可以枚举 xx 的倍数,把他们的位置记作 seq1,seq2,,seqmseq_1,seq_2,…,seq_m,特别地,seq0=0seq_0=0

那么对于任意一个 i(1i<m)i(1\le i< m) 满足 l[seqi1+1,seqi]l\in[seq_{i-1}+1,seq_{i}]r[seqi+1,n]r\in[seq_{i+1},n] 的区间 [l,r][l,r] 可能会对 xx 产生贡献。

另外,注意到这些区间可能会被最大的区间覆盖,所以我们记录一个 posipos_i,表示以 ii 未左端点的为贡献价值的区间最长能延伸到哪个位置。显然若区间 [l,r][l,r] 的子区间 [l,r][l',r'] 贡献了价值,那么 [l,r][l,r] 也已被贡献了价值。因此,[i,posi][i,pos_i] 的所有以 ii 为左端点的子区间均未贡献价值。并且,pospos 显然是单调递增的。

具体来说,我们用线段树维护 pospos 数组,对于任意 i(1i<m)i(1\le i<m)

  1. 查询区间 [seqi1+1,seqi][seq_{i-1}+1,seq_i]pospos 值第一个大于等于 seqi+1seq_i+1 的位置,设其为 ll
  2. 将区间 [l,seqi][l,seq_i] 中的 pospos 值全部改为 seqi+11seq_{i+1}-1

操作完成后,所有 posipos_i 操作前后的差值,即线段树全局和的插值就是对 xx 的贡献。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 1e5 + 10;
int n, a[N], pos[N], len, seq[N];
ll ans[N];
#define pl p << 1

#define pr p << 1 | 1

#define S_T_T int

struct Segment_Tree {
    struct Tree {
        int l, r;
        int tag, mx;
        ll sum;
    } a[N * 4];
    void pushup(int p) {
        a[p].sum = a[pl].sum + a[pr].sum;
        a[p].mx = max(a[pl].mx, a[pr].mx);
    }
    void pushdown(int p) {
        if (a[p].tag != -1) {
            a[pl].sum = 1ll * (a[pl].r - a[pl].l + 1) * a[p].tag;
            a[pl].mx = a[p].tag;
            a[pl].tag = a[p].tag;
            a[pr].sum = 1ll * (a[pr].r - a[pr].l + 1) * a[p].tag;
            a[pr].mx = a[p].tag;
            a[pr].tag = a[p].tag;
            a[p].tag = -1;
        }
    }
    void build(int p, int l, int r) {
        a[p].l = l;
        a[p].r = r;
        a[p].tag = -1;
        if (l == r) {
            a[p].sum = a[p].mx = n;
            return;
        }
        int mid = (l + r) >> 1;
        build(pl, l, mid);
        build(pr, mid + 1, r);
        pushup(p);
    }
    void change(int p, int l, int r, ll v) {
        if (l <= a[p].l && a[p].r <= r) {
            a[p].sum = 1ll * (a[p].r - a[p].l + 1) * v;
            a[p].mx = a[p].tag = v;
            return;
        }
        pushdown(p);
        int mid = (a[p].l + a[p].r) >> 1;
        if (l <= mid)
            change(pl, l, r, v);
        if (r > mid)
            change(pr, l, r, v);
        pushup(p);
    }
    S_T_T ask(int p, int l, int r, ll v) {
        if (a[p].l == a[p].r) {
            if (a[p].mx < v || a[p].l > r)
                return -1;
            if (a[p].l < l)
                return l;
            return a[p].l;
        }
        pushdown(p);
        if (a[pl].mx >= v)
            return ask(pl, l, r, v);
        else
            return ask(pr, l, r, v);
    }
} tree;
int main() {
    freopen("gcd.in", "r", stdin);
    freopen("gcd.out", "w", stdout);
    n = read();
    for (int i = 1; i <= n; i++) {
        a[i] = read();
        pos[a[i]] = i;
    }
    tree.build(1, 1, n);
    for (int x = n; x >= 1; x--) {
        len = 0;
        for (int i = x; i <= n; i += x) seq[++len] = pos[i];
        sort(seq + 1, seq + len + 1);
        ans[x] = tree.a[1].sum;
        for (int i = 1; i < len; i++) {
            int l = tree.ask(1, seq[i - 1] + 1, seq[i], seq[i + 1]);
            if (l != -1)
                tree.change(1, l, seq[i], seq[i + 1] - 1);
        }
        ans[x] -= tree.a[1].sum;
    }
    for (int i = 1; i <= n; i++) {
        write(ans[i]);
        putchar('\n');
    }
    return 0;
}

C. 走出迷宫\color{#9D3DCF}\text{C. 走出迷宫}

题目

小奇被困在了一个 N×MN\times M 的迷宫中,迷宫的某些格子有着障碍,无法通过。出口在小奇的右方,因此小奇每一步只能向上、右、下三个方向行走。

由于某些神秘力量的作用,小奇和出口的位置会不断改变,同时迷宫的构造也有可能改变。

你要做的就是帮助小奇算出对于每种情况,小奇最少要走多少步才能到达出口。如果小奇无论怎样都无法走出迷宫,请输出 -1

题解

nn 很小,mm 很大可以想到使用线段树维护。

对于一个区间 [l,r][l,r] 我们认为它代表从第 ll 列走到第 rr 列,设 f[l,r][i][j]f_{[l,r]}[i][j] 表示从 (l,i)(l,i) 走到 (r,j)(r,j) 的最短长度,显然可以得到状态转移方程 f[l,r][i][j]=min{f[l,mid][i][k]+1+f[mid+1,r][k][j]}f_{[l,r]}[i][j]=\min\{f_{[l,mid]}[i][k]+1+f_{[mid+1,r]}[k][j]\},直接维护即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 6, M = 2.1e5 + 10;
ll n, m, q, c[N][M], inf = 0x3f3f3f3f;
#define pl p << 1

#define pr p << 1 | 1

#define S_T_T ll

struct Segment_Tree {
    struct Tree {
        int l, r;
        S_T_T cx[N][N];
    } a[M * 4], ans;
    void pushup(int p) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                a[p].cx[i][j] = inf;
                for (int k = 1; k <= n; k++)
                    a[p].cx[i][j] = min(a[p].cx[i][j], a[pl].cx[i][k] + 1 + a[pr].cx[k][j]);
            }
        }
    }
    void build(int p, int l, int r) {
        a[p].l = l;
        a[p].r = r;
        if (l == r) {
            for (int i = 1; i <= n; i++)
                for (int j = i; j <= n; j++) a[p].cx[i][j] = a[p].cx[j][i] = j - i;
            return;
        }
        int mid = (l + r) >> 1;
        build(pl, l, mid);
        build(pr, mid + 1, r);
        pushup(p);
    }
    void change(int p, int x, int y) {
        if (a[p].l == a[p].r) {
            if (a[p].cx[x][x] == 0) {
                for (int i = 1; i <= x; i++) {
                    for (int j = x; j <= n; j++) a[p].cx[i][j] = a[p].cx[j][i] = inf;
                }
            } else {
                a[p].cx[x][x] = 0;
                for (int i = 1; i < x; i++) {
                    a[p].cx[i][x] = a[p].cx[x][i] = a[p].cx[i][x - 1] + 1;
                    for (int j = x + 1; j <= n; j++) {
                        a[p].cx[i][j] = a[p].cx[j][i] = a[p].cx[i][x - 1] + 2 + a[p].cx[x + 1][j];
                    }
                }
                for (int i = x + 1; i <= n; i++) {
                    a[p].cx[x][i] = a[p].cx[i][x] = 1 + a[p].cx[x + 1][i];
                }
            }
            return;
        }
        int mid = (a[p].l + a[p].r) >> 1;
        if (y <= mid)
            change(pl, x, y);
        if (y > mid)
            change(pr, x, y);
        pushup(p);
    }
    Tree ask(int p, int y1, int y2) {
        if (y1 <= a[p].l && a[p].r <= y2) {
            return a[p];
        }
        int mid = (a[p].l + a[p].r) >> 1;
        if (y1 <= mid && y2 > mid) {
            Tree ans, tl = ask(pl, y1, y2), tr = ask(pr, y1, y2);
            ans.l = tl.l;
            ans.r = tr.r;
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++) {
                    ans.cx[i][j] = inf;
                    for (int k = 1; k <= n; k++)
                        ans.cx[i][j] = min(ans.cx[i][j], tl.cx[i][k] + 1 + tr.cx[k][j]);
                }
            return ans;
        }
        if (y1 <= mid)
            return ask(pl, y1, y2);
        return ask(pr, y1, y2);
    }
} tree;
int main() {
    freopen("maze.in", "r", stdin);
    freopen("maze.out", "w", stdout);
    n = read();
    m = read();
    q = read();
    tree.build(1, 1, m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            c[i][j] = read();
            if (c[i][j] == 0)
                tree.change(1, i, j);
        }
    }
    while (q--) {
        int opt, a, b, c, d;
        opt = read();
        a = read();
        b = read();
        if (opt == 1) {
            tree.change(1, a, b);
        } else {
            c = read();
            d = read();
            if (b > d) {
                cout << -1 << endl;
                continue;
            }
            tree.ans = tree.ask(1, b, d);
            if (tree.ans.cx[a][c] >= inf)
                write(-1);
            else
                write(tree.ans.cx[a][c]);
            putchar('\n');
        }
    }
    return 0;
}

LCA 和倍增

100+35+0=135/Rank 6\color{Green}100\color{Black}+\color{Orange}35\color{Black}+\color{Red}0\color{Black}=\color{#92E411}135\color{Black}/\text{Rank 6}

A. 跑步打卡\color{#9D3DCF}\text{A. 跑步打卡}

题目

小c 同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

这个游戏的地图可以看作一一棵包含 nn 个结点和 n1n-1 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 11nn 的连续正整数。

现在有 mm 个玩家,第 ii 个玩家的起点为 sis_i,终点为 tit_i。每天打卡任务开始时,所有玩家在第 00 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树,所以每个人的路径是唯一的)

小c 想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点 jj 的观察员会选择在第 wjw_j 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 wjw_j 秒也正好到达了结点 jj小c 想知道每个观察员会观察到多少人?

注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点 jj 作为终点的玩家:若他在第 wjw_j 秒前到达终点,则在结点 jj 的观察员不能观察到该玩家;若他正好在第 wjw_j 秒到达终点,则在结点 jj 的观察员可以观察到这个玩家。

题解

这题就是P1600 [NOIP2016 提高组] 天天爱跑步

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 3e5 + 10;
int n, m, w[N], s[N], t[N], c1[N], c2[2 * N], ans[N];
int head[N], nxt[2 * N], ver[2 * N], tot;
void add(int x, int y) {
    ver[++tot] = y;
    nxt[tot] = head[x];
    head[x] = tot;
}
int fa[N][20], lg[N], dep[N];
struct asdf {
    int t, v;
} e;
vector<asdf> d[2][N];
void dfs(int x, int f) {
    fa[x][0] = f;
    dep[x] = dep[f] + 1;
    for (int i = 1; i <= lg[dep[x]]; i++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
    for (int i = head[x]; i; i = nxt[i])
        if (ver[i] ^ f)
            dfs(ver[i], x);
}
void init() {
    for (int i = 1, j = 1; i <= n; i++) {
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }
    dfs(1, 0);
}
int ask_lca(int u, int v) {
    if (dep[u] < dep[v])
        swap(u, v);
    while (dep[u] > dep[v]) u = fa[u][lg[dep[u] - dep[v]] - 1];
    if (u == v)
        return u;
    for (int i = lg[dep[u]] - 1; i >= 0; i--) {
        if (fa[u][i] != fa[v][i]) {
            u = fa[u][i];
            v = fa[v][i];
        }
    }
    return fa[u][0];
}
void Add(int t, int x, int y, int v) {
    e.t = y;
    e.v = v;
    d[t][x].push_back(e);
}
void dfs2(int x, int f) {
    int cnt1 = c1[w[x] + dep[x]], cnt2 = c2[w[x] - dep[x] + (int)3e5];
    for (int i = 0; i < d[0][x].size(); i++) c1[d[0][x][i].t] += d[0][x][i].v;
    for (int i = 0; i < d[1][x].size(); i++) c2[d[1][x][i].t] += d[1][x][i].v;
    for (int i = head[x]; i; i = nxt[i]) {
        int y = ver[i];
        if (y == f)
            continue;
        dfs2(y, x);
    }
    ans[x] = c1[w[x] + dep[x]] - cnt1 + c2[w[x] - dep[x] + (int)3e5] - cnt2;
}
int main() {
    freopen("running.in", "r", stdin);
    freopen("running.out", "w", stdout);
    n = read();
    m = read();
    for (int i = 1, u, v; i < n; i++) {
        u = read();
        v = read();
        add(u, v);
        add(v, u);
    }
    init();
    for (int i = 1; i <= n; i++) {
        w[i] = read();
    }
    for (int i = 1; i <= m; i++) {
        s[i] = read();
        t[i] = read();
        int lca = ask_lca(s[i], t[i]);
        Add(0, s[i], dep[s[i]], 1);
        Add(0, fa[lca][0], dep[s[i]], -1);
        Add(1, t[i], dep[s[i]] - 2 * dep[lca] + 3e5, 1);
        Add(1, lca, dep[s[i]] - 2 * dep[lca] + 3e5, -1);
    }
    dfs2(1, 0);
    for (int i = 1; i <= n; i++) {
        cout << ans[i] << " ";
    }
    return 0;
}

B. 抽奖游戏\color{#9D3DCF}\text{B. 抽奖游戏}

题目

nn 个人来游戏厅玩抽奖游戏。抽奖游戏规则如下:每一轮游戏者投入一个硬币,如果抽奖机转出了 bingo\texttt{bingo},则会掉出两个硬币,否则将什么都不会发生。

抽奖机的抽奖结果以 mm 为周期,并且你知道一个周期中每一次的结果。nn 个人每个人都带着一些钱,他们会从第 11 个人开始玩,第 11 个人玩完后是第 22 个人玩,第 22 个人玩完后是第 33 个人玩,第 i(i<n)i(i<n) 个人玩完后是第 i+1i+1 个人玩,第 nn 个人玩完后是第 11 个人玩,直到某个人没钱了,他们就会一起离开。

求最早在玩第几次游戏后他们会离开,或者他们会一直在这里玩下去。

题解

显然每个人的抽奖是不会互相影响的,那么我们可以分别求出每个人最多能玩到的次数,取最小值即可。

gi,jg_{i,j} 表示一个人从某一周期第 ii 轮开始玩,玩了 2j2^j 次的累计收益,mgi,jmg_{i,j} 表示一个人从某一周期第 ii 轮开始玩,玩了 2j2^j 次的最小累计收益。

预处理处 ggmgmg,易知同一个人玩的第 ii 次和第 i+mi+m 次是同一轮,所以每 mm 局的收益都相同。因此,利用倍增求出前 mm 次的累计收益 sumsum,和这 mm 次当中累计收益的最小值 mxmx,记第 ii 个人原有的硬币数位 aia_i,那么如果 sum0sum\ge 0ai+mx>0a_i+mx>0,则这个人永远不会花完硬币。

如果前 mm 次不会花完硬币,那么假设 mxmx 是玩前 xx 次后的收益,计算玩前 xx 次后,至少能再玩几组游戏,mm 次一组,记组数为 pp,问题转化,从头开始每 mm 个一组,这个人先玩了 p×mp\times m 次,再玩了 xx 次,如果玩了 p×m+xp\times m+x 次之后不会离开,那么一定能玩到 (p+1)×m(p+1)\times m 次,因为第 p×m+xp\times m+x 次的累计收益是第 p×m+1(p+1)×mp\times m+1\sim (p+1)\times m 次的最小值。

算完 (p+1)×m(p+1)\times m 次后,用倍增计算还能玩几次即可。

位移时 long long\texttt{long long} 一定要多开,否则会 RE\text{RE},但数组不用,因为这题空间限制很小。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 1e6 + 10, M = 21;
int n, m;
int a[N], g[N][M], mg[N][M];
ll ans[N], res;
char c[N];
int main() {
    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);
    n = read();
    for (int i = 0; i < n; i++) a[i] = read();
    m = read();
    for (int i = 0; i < m; i++) cin >> c[i];
    for (int i = 0; i < m; i++) mg[i][0] = g[i][0] = (c[i] == 'W' ? 1 : -1);
    for (int j = 1; j <= 19; j++) {
        for (ll i = 0; i < m; i++) {
            g[i][j] = g[i][j - 1] + g[(i + ((1ll << (j - 1)) * n)) % m][j - 1];
            mg[i][j] = min(mg[i][j - 1], g[i][j - 1] + mg[(i + ((1ll << (j - 1)) * n)) % m][j - 1]);
        }
    }
    for (int i = 0; i < n; i++) {
        ll ju = i % m, sum = 0, mx = 0;
        for (int j = 19; j >= 0; j--) {
            if (m & (1ll << j)) {
                mx = min(mx, sum + mg[ju][j]);
                sum += g[ju][j];
                ju = (ju + (1ll << j) * n % m) % m;
            }
        }
        if (sum >= 0 && a[i] + mx > 0) {
            ans[i] = -1;
            continue;
        }
        sum = -sum;
        mx = -mx;
        if (a[i] > mx) {
            ans[i] = 1ll * (a[i] - mx) / sum * m;
            a[i] = (a[i] - mx) % sum + mx;
            if (a[i] > mx) {
                a[i] -= sum;
                ans[i] += 1ll * m;
            }
        }
        ju = i % m;
        for (int j = 19; j >= 0; j--) {
            if (a[i] + mg[ju][j] > 0) {
                a[i] += mg[ju][j];
                ans[i] += (1ll << j);
                ju = (ju + (1ll << j) * n % m) % m;
            }
        }
        ans[i]++;
    }
    res = -1;
    for (int i = 0; i < n; i++) {
        if (ans[i] != -1) {
            if (res == -1 || res > 1ll * ans[i] * (i + 1) + 1ll * (ans[i] - 1ll) * (n - i - 1))
                res = 1ll * ans[i] * (i + 1) + 1ll * (ans[i] - 1ll) * (n - i - 1);
        }
    }
    write(res);
    return 0;
}

C. 景区旅行\color{#9D3DCF}\text{C. 景区旅行}

题目

T 城是一个旅游城市,具有 nn 个景点和 mm 条道路,所有景点编号为 1n1\sim n。每条道路连接这 nn 个景区中的某两个景区,道路是单向通行的。每条道路都有一个长度。

为了方便旅游,每个景点都有一个加油站。第 ii 个景点的加油站的费用为 pip_i,加油量为 cic_i。若汽车在第 ii 个景点加油,则需要花费 pip_i 元钱,之后车的油量将被加至油量上限与 cic_i 中的较小值。不过如果加油前汽车油量已经不小于 cic_i,则不能在该景点加油。

小 C 准备来到 T 城旅游。他的汽车油量上限为 CC。旅游开始时,汽车的油量为 00。在旅游过程中:

  1. 当汽车油量大于 00 时,汽车可以沿从当前景区出发的任意一条道路到达另一个景点(不能只走道路的一部分),汽车油量将减少 11
  2. 当汽车在景点 ii 且当前油量小于 cic_i 时,汽车可以在当前景点加油,加油需花费 pip_i 元钱,这样汽车油量将变为 min{ci,C}\min\{c_i,C\}

一次旅游的总花费等于每次加油的花费之和,旅游的总路程等于每次经过道路的长度之和。注意多次在同一景点加油,费用也要计算多次,同样地,多次经过同一条道路,路程也要计算多次。

小 C 计划旅游 T 次,每次旅游前,小 C 都指定了该次旅游的起点和目标路程。由于行程不同,每次出发前带的钱也不同。为了省钱,小 C 需要在旅游前先规划好旅游路线(包括旅游的路径和加油的方案),使得从起点出发,按照该旅游路线旅游结束后总路程不小于目标路程,且剩下的钱尽可能多。请你规划最优旅游路线,计算这 T 次旅游每次结束后最多可以剩下多少钱。

题解

fi,qf_{i,q} 表示当前位于景点 ii,下次在景点 ii 位置加油,剩下钱数为 qq 时能走的最大路程。

q<viq<v_i 时,fi,qf_{i,q} 等于 00,否则,枚举下次加油的位置,得到:

fi,j=max{fj,qvi+w(i,j,min(xi,C))}f_{i,j}=\max\{f_{j,q-v_i}+w_{(i,j,\min(x_i,C))}\}

其中 wi,j,cw_{i,j,c} 为从 iijj 经过不超过 cc 条道路的最大路程,要求 wi,j,cw_{i,j,c},可以枚举 ii 出发的第一条路 ee

wi,j,c=maxae=i{wbe,j,c1+le}w_{i,j,c}=\max_{a_e=i}\{w_{b_e,j,c-1}+l_e\}

边界:wi,i,0=0,wi,j,0=(ij)w_{i,i,0}=0,w_{i,j,0}=-\infty(i\neq j)。由于 cc 每次只变化 11,我们便可以用倍增预处理从 iijj 经过不超过 2k2^k 条道路的最大路程 gi,j,kg_{i,j,k},则当 k>0k>0 时:

gi,j,k=maxx{gi,x,k1+gx,j,k1}g_{i,j,k}=\max_x\{g_{i,x,k-1}+g_{x,j,k-1}\}

注意到我们只需要处理出 wi,j,min(xi,C)w_{i,j,\min(x_i,C)},令 c=min(xi,C)c=\min(x_i,C),那么对 cc 进行二进制拆分计算,可得:

wi,j,c=maxx{max(wi,x,c2k+gx,j,k,wi,j,c2k)}w_{i,j,c}=\max_x\{\max(w_{i,x,c-2^k}+g_{x,j,k},w_{i,j,c-2^k})\}

询问时二分最小的 ansans 使得 fsi,ansdif_{s_i,ans}\ge d_i

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 110, M = 1e3 + 110, C = 1e5 + 110;
int n, m, c, t, v[N], x[N];
ll f[N][N * N], w[N][N], g[N][N][18], pre[N][N], mx[N][N * N], edge[M];
int head[N], ver[M], nxt[M], tot;
void add(int x, int y, ll z) {
    ver[++tot] = y;
    edge[tot] = z;
    nxt[tot] = head[x];
    head[x] = tot;
}
void init() {
    memset(g, -1, sizeof(g));
    memset(w, -1, sizeof(w));
    memset(f, 128, sizeof(f));
    for (int i = 1; i <= n; i++) g[i][i][0] = 0;
    for (int xx = 1; xx <= n; xx++)
        for (int i = head[xx]; i; i = nxt[i]) {
            int y = ver[i];
            g[xx][y][0] = max(g[xx][y][0], edge[i]);
        }
    for (int i = 1; i <= 17; i++)
        for (int xx = 1; xx <= n; xx++)
            for (int y = 1; y <= n; y++) {
                g[xx][y][i] = g[xx][y][i - 1];
                for (int k = 1; k <= n; k++)
                    if (g[xx][k][i - 1] != -1 && g[k][y][i - 1] != -1)
                        g[xx][y][i] = max(g[xx][y][i], g[xx][k][i - 1] + g[k][y][i - 1]);
            }
    for (int p = 1; p <= n; p++) {
        int sum = x[p];
        w[p][p] = 0;
        for (int i = 17; i >= 0; i--)
            if (sum >= (1 << i)) {
                sum -= (1 << i);
                for (int u = 1; u <= n; u++) pre[p][u] = w[p][u];
                for (int u = 1; u <= n; u++)
                    for (int v = 1; v <= n; v++)
                        if (pre[p][u] != -1 && g[u][v][i] != -1)
                            w[p][v] = max(w[p][v], pre[p][u] + g[u][v][i]);
            }
    }
    for (int i = 0; i <= n * n; i++)
        for (int u = 1; u <= n; u++) {
            if (v[u] > i) {
                f[u][i] = 0;
                continue;
            }
            for (int v_ = 1; v_ <= n; v_++)
                if (w[u][v_] != -1)
                    f[u][i] = max(f[u][i], f[v_][i - v[u]] + w[u][v_]);
        }
    for (int u = 1; u <= n; u++)
        for (int i = 1; i <= n * n; i++) mx[u][i] = max(f[u][i], mx[u][i - 1]);
}
int main() {
    freopen("trip.in", "r", stdin);
    freopen("trip.out", "w", stdout);
    n = read();
    m = read();
    c = read();
    t = read();
    for (int i = 1; i <= n; i++) {
        v[i] = read();
        x[i] = min(read(), (ll)c);
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        ll z;
        u = read();
        v = read();
        z = read();
        add(u, v, z);
    }
    init();
    for (int i = 1; i <= t; i++) {
        ll s, q, d, l = 0, r;
        s = read();
        r = q = read();
        d = read();
        if (mx[s][q] < d) {
            puts("-1");
            continue;
        }
        while (l < r) {
            ll mid = (l + r) >> 1;
            if (mx[s][mid] >= d)
                r = mid;
            else
                l = mid + 1;
        }
        write(q - l);
        putchar('\n');
    }
    return 0;
}
posted @ 2022-08-07 23:18  luckydrawbox  阅读(76)  评论(0)    收藏  举报  来源