20250807

T1

弹射器

考虑倍增,需要求跳了 \(2^i\) 步往左往右最远跳到哪。我们考虑跳 \(2^{i - 1}\) 步能跳到的范围,那么跳 \(2^i\) 步的范围肯定是上一个范围中往左往右再跳 \(2^{i - 1}\) 跳到位置的 \(\min\)\(\max\)。于是对每一层倍增开 ST 表查询区间最值即可。倍增确定答案,时空复杂度均为 \(\mathcal{O}(n\log^2 n)\)

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
int n;
int a[100005];
struct S {
    int tl[100005], tr[100005];
    int mx[18][100005], mn[18][100005];
    void build() {
        for (int i = 1; i <= n; i++) mn[0][i] = tl[i], mx[0][i] = tr[i];
        for (int i = 1; (1 << i) <= n; i++) {
            for (int j = 1; j + (1 << i) - 1 <= n; j++) {
                mn[i][j] = min(mn[i - 1][j], mn[i - 1][j + (1 << (i - 1))]);
                mx[i][j] = max(mx[i - 1][j], mx[i - 1][j + (1 << (i - 1))]);
            }
        }
    }
    int qmax(int l, int r) {
        int k = 31 - __builtin_clz(r - l + 1);
        return max(mx[k][l], mx[k][r - (1 << k) + 1]);
    }
    int qmin(int l, int r) {
        int k = 31 - __builtin_clz(r - l + 1);
        return min(mn[k][l], mn[k][r - (1 << k) + 1]);
    }
} x[18];
int L[100005], R[100005];
int tl[100005], tr[100005];
int pmn[100005], smx[100005];
int main() {
    freopen("jump.in", "r", stdin);
    freopen("jump.out", "w", stdout);
    cin >> n;
    pmn[0] = 2147483647;
    for (int i = 1; i <= n; i++) cin >> a[i], x[0].tl[i] = max(1, i - a[i]), x[0].tr[i] = min(n, i + a[i]);
    x[0].build();
    for (int i = 1; i < 18; i++) {
        for (int j = 1; j <= n; j++) {
            x[i].tl[j] = x[i - 1].qmin(x[i - 1].tl[j], x[i - 1].tr[j]);
            x[i].tr[j] = x[i - 1].qmax(x[i - 1].tl[j], x[i - 1].tr[j]);
        }
        x[i].build();
    }
    for (int i = 1; i <= n; i++) L[i] = R[i] = i;
    int ans = 0;
    for (int i = 17; ~i; i--) {
        bool no = 0;
        for (int j = 1; j <= n; j++) tl[j] = x[i].qmin(L[j], R[j]), tr[j] = x[i].qmax(L[j], R[j]);
        for (int j = 1; j <= n; j++) pmn[j] = min(pmn[j - 1], tr[j]);
        for (int j = 1; j <= n; j++) no |= (tl[j] != 1 && pmn[tl[j] - 1] < j);
        for (int j = n; j; j--) smx[j] = max(smx[j + 1], tl[j]);
        for (int j = 1; j <= n; j++) no |= (tr[j] != n && smx[tr[j] + 1] > j);
        if (no) {
            ans |= (1 << i);
            for (int j = 1; j <= n; j++) L[j] = tl[j], R[j] = tr[j];
        }
    }
    cout << ans + 1 << "\n";
    return 0;
}

T2

我们

枚举右端点,左端点只有本质不同的 \(\log\) 种价值。对每一段二分出最晚不合法位置在哪即可。复杂度 \(\mathcal{O}(n\log n\log V)\)\(1e6\) 随便过。虽然赛后被魔怔同学卡了。

代码
#include <iostream>
#include <algorithm>
// #define int long long
using namespace std;
namespace Fread {
    #define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++
    char buf[1<<21], *p1, *p2, ch;
    long long read() {
        long long ret = 0, neg = 0; char c = getchar(); neg = (c == '-');
        while (c < '0' || c > '9') c = getchar(), neg |= (c == '-');
        while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
        return ret * (neg ? -1 : 1);
    }
}
using Fread::read;
int n;
long long a[1000005];
int p1[1000005];
long long v1[1000005];
int sz1;
int p2[1000005];
long long v2[1000005];
int rem[1000005];
int sz2;
long long mx[21][1000005];
bool b1[1000005], b2[1000005];
int tmp[125], t;
long long Qmax(int l, int r) {
    int k = 31 - __builtin_clz(r - l + 1);
    return max(mx[k][l], mx[k][r - (1 << k) + 1]);
}
signed main() {
    freopen("us.in", "r", stdin);
    freopen("us.out", "w", stdout);
    int tc = read();
    while (tc--) {
        long long Ans = sz1 = sz2 = 0;
        n = read();
        for (int i = 1; i <= n; i++) mx[0][i] = a[i] = read();
        for (int i = 1; (1 << i) <= n; i++) {
            for (int j = 1; j + (1 << i) - 1 <= n; j++) 
                mx[i][j] = max(mx[i - 1][j], mx[i - 1][j + (1 << (i - 1))]);
        }
        v1[0] = v2[0] = -1;
        for (int i = 1, k; i <= n; i++) {
            for (int j = 1; j <= sz1; j++) v1[p1[j]] &= a[i];
            p1[++sz1] = i, v1[i] = a[i]; p1[sz1 + 1] = 0;
            k = 0;
            for (int j = 1; j <= sz1; j++) rem[j] = (v1[p1[j]] != v1[p1[j + 1]]);
            for (int j = 1; j <= sz1; j++) rem[j] ? (p1[++k] = p1[j], b1[p1[j]] = 1) : (b1[p1[j]] = 0);
            sz1 = k;

            for (int j = 1; j <= sz2; j++) v2[p2[j]] |= a[i];
            p2[++sz2] = i, v2[i] = a[i]; p2[sz2 + 1] = 0;
            k = 0;
            for (int j = 1; j <= sz2; j++) rem[j] = (v2[p2[j]] != v2[p2[j + 1]]);
            for (int j = 1; j <= sz2; j++) rem[j] ? (p2[++k] = p2[j], b2[p2[j]] = 1) : (b2[p2[j]] = 0);
            sz2 = k;

            merge(p1 + 1, p1 + sz1 + 1, p2 + 1, p2 + sz2 + 1, tmp + 1);
            t = sz1 + sz2;
            tmp[t + 1] = i + 1;
            for (long long j = t, va = 0, vo = 0; j; j--) {
                int pl = tmp[j - 1] + 1, pr = tmp[j];
                if (pl > pr) 
                    continue;
                if (b1[pr]) 
                    va = v1[pr];
                if (b2[pr]) 
                    vo = v2[pr];
                long long val = va ^ vo;
                int l = pl, r = pr, mid, ans = pr + 1;
                while (l <= r) {
                    mid = (l + r) >> 1;
                    if (Qmax(mid, i) <= val) 
                        ans = mid, r = mid - 1;
                    else 
                        l = mid + 1;
                }
                Ans += (pr - ans + 1);
            }
        }
        cout << Ans << "\n";
    }
    return 0;
}

T3

没有重复颜色

朴素的做法可以考虑线段树分治套线段树。这里我们考虑写单侧递归线段树。记每个点前面第一个同色点为 \(lst_i\),则每个点的最早合法左端点即为前缀 \(lst\) 的最大值。我们考虑单侧递归线段树,设 \(c(x, p)\) 为计算左端点不小于 \(x\) 的情况下右端点在线段树点 \(p\) 区间内的最大答案。分类讨论,若 \(x \ge mx_{lson_p}\),则左儿子所有点贡献变成 \(pre_i - pre_{x - 1}\),又 \(mid\) 处值最大,即左儿子对答案贡献为 \(pre_{mid} - pre_{x - 1}\)。然后递归计算右儿子:\(c(x, rson_p)\)。若 \(x < mx_{lson_p}\),则右儿子答案可以直接拿来用,递归计算左儿子。这里的右儿子答案在 pushup 时维护,为 \(c(mx_{lson_p}\space, rson_p)\)。然后查询的时候对于区间内的 \(lst\) 前缀限制,我们利用原本维护的东西来限制。对于区间外的 \(lst\) 前缀限制,我们利用 \(x\),即每次递归右儿子时 \(x \leftarrow \max(x, mx_{lson_p}\space)\)。然后再开个 set 维护每个颜色的位置即可做完。

代码
#include <iostream>
#include <set>
#define int long long
using namespace std;
int n, m, q;
long long pre[200005], a[200005];
int lst[200005], ap[200005];
int c[2000005];
struct Segment_Tree {
    int val[800005], mx[800005];
    int Query(int o, int l, int r, int v) {
        if (l == r) 
            return pre[r] - pre[max(mx[o], v) - 1];
        int mid = (l + r) >> 1;
        if (mx[o << 1] < v) 
            return max(pre[mid] - pre[v - 1], Query(o << 1 | 1, mid + 1, r, v));
        else 
            return max(Query(o << 1, l, mid, v), val[o]);
    }
    void pushup(int o, int l, int r) {
        int mid = (l + r) >> 1;
        mx[o] = max(mx[o << 1], mx[o << 1 | 1]);
        val[o] = Query(o << 1 | 1, mid + 1, r, mx[o << 1]);
    }
    void Build(int o, int l, int r) {
        if (l == r) 
            return mx[o] = lst[l] + 1, void();
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
        pushup(o, l, r);
    }
    void Change(int o, int l, int r, int x, int y) {
        if (l == r) 
            return mx[o] = y, void();
        int mid = (l + r) >> 1;
        if (x <= mid) 
            Change(o << 1, l, mid, x, y);
        else 
            Change(o << 1 | 1, mid + 1, r, x, y);
        pushup(o, l, r);
    }
    int Query(int o, int l, int r, int L, int R, int v) {
        if (L <= l && r <= R) 
            return Query(o, l, r, v);
        int mid = (l + r) >> 1;
        if (R <= mid) 
            return Query(o << 1, l, mid, L, R, v);
        if (L > mid) 
            return Query(o << 1 | 1, mid + 1, r, L, R, max(v, mx[o << 1]));
        return max(Query(o << 1, l, mid, L, R, v), Query(o << 1 | 1, mid + 1, r, L, R, max(v, mx[o << 1])));
    }
} seg;
int val[200005], L[200005];
set<int> st[200005];
void Change(int x, int y) {
    int o = c[x];
    if (o == y) 
        return;
    set<int>::iterator it = st[o].upper_bound(x); st[o].erase(prev(it));
    if (it != st[o].end()) {
        int z = *it;
        if (it != st[o].begin()) {
            int w = *(--it);
            seg.Change(1, 1, n, z, w + 1);
        } else 
            seg.Change(1, 1, n, z, 1);
    }
    it = st[y].lower_bound(x);
    if (it != st[y].end()) {
        int z = *it;
        seg.Change(1, 1, n, z, x + 1);
    }
    if (it != st[y].begin()) {
        int w = *(--it);
        seg.Change(1, 1, n, x, w + 1);
    } else 
        seg.Change(1, 1, n, x, 1);
    st[y].insert(x);
    c[x] = y;
}
signed main() {
    freopen("norepeat.in", "r", stdin);
    freopen("norepeat.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++) cin >> a[i], pre[i] = pre[i - 1] + a[i];
    for (int i = 1; i <= n; i++) cin >> c[i], lst[i] = ap[c[i]], ap[c[i]] = i, st[c[i]].insert(i);
    seg.Build(1, 1, n);
    while (q--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1) 
            cout << seg.Query(1, 1, n, x, y, x) << "\n";
        else 
            Change(x, y);
    }
    return 0;
}

T4

卡牌游戏

先考虑 \(tp = 0\)。我们直接 dp,考虑根号分治优化转移。若当前值小于根号,则拿前面的往自己贡献。否则对于前面的小数,枚举并贡献过来。对于前面的大数,我们枚举倍数贡献过来。这里我们可以把贡献放缩掉,原本贡献是 \(S\) 之内最大的 lcm 的倍数,现在我们放缩为任意 lcm 的倍数,这样我们就可以直接枚举贡献到底是多少,并从这个位置转移过来。于是这样我们就做完了 \(tp = 0\)

考虑 \(tp = 1\)。我们考虑枚举一个后缀最后选的东西,尝试拿它和每个前缀最后一个选的东西拼一拼。会发现我们的转移至多不会跨过三个小点,因为可以注意到两个小点的贡献一定 \(\ge \frac{S}{2}\),若跨过三个小点,直接选上答案至少增加 \(S\),而减少一定不超过 \(S\),因此不会变劣。然后对于大点来说,还是枚举倍数,那么此时我们只需要对每种贡献记录上一个它的因数的大点和再上一个的位置,因为我们的转移一定不会同时跨过这两个东西(设上一个位置 \(x\),再上一个 \(y\),若断点在 \(y\) 左,则把两个都选上得到的贡献和 \(\ge S\),原本 \(\le S\),所以不会劣。\((y, x)\) 区间内点也同理。若干掉 \(x\),则用 \(y\) 来更新 \(x\) 即可)。那么这样合并就也是根号的了。然后就做完了。最好加一点卡常。

代码
#pragma GCC optimize("Ofast,inline,unroll-loops")
#include <iostream>
#include <algorithm>
#include <string.h>
#include <math.h>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
inline void Cmax(int &x, int y) { x = max(x, y); }
int B;
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++
char buf[1<<21], *p1, *p2, ch;
long long read() {
    long long ret = 0, neg = 0; char c = getchar(); neg = (c == '-');
    while (c < '0' || c > '9') c = getchar(), neg |= (c == '-');
    while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
    return ret * (neg ? -1 : 1);
}
int g[751][751];
int n, S, tp, id;
inline int F(int a, int b) {
    a > b ? swap(a, b) : void();
    return S - S % (a * b / g[a][b % a]);
}
int pre[500005], nxt[500005];
namespace U {
int buf[500005];
void work(int *a, int *dp) {
    memset(buf, -63, sizeof buf);
    for (int i = 1; i <= n; i++) pre[i] = (a[i] <= B ? i : pre[i - 1]);
    nxt[n + 1] = n + 1; for (int i = n; i; i--) nxt[i] = (a[i] <= B ? i : nxt[i + 1]);
    dp[0] = -inf;
    for (int i = 1; i <= n; i++) {
        if (a[i] > B) for (int j = 0; j <= S; j += a[i]) dp[i] = max(dp[i], buf[j] + j);
        for (int j = pre[i - 1], c = 1; j && c <= 3; c++, j = pre[j - 1]) dp[i] = max(dp[i], dp[j] + F(a[j], a[i]));
        for (int j = nxt[i + 1], c = 1; j <= n && c <= 3; c++, j = nxt[j + 1]) dp[j] = max(dp[j], dp[i] + F(a[i], a[j]));
        if (a[i] > B) for (int j = 0; j <= S; j += a[i]) buf[j] = max(buf[j], dp[i]);
    }
}
}
int a[500005], ans1, ans2;
int dp[2][500005];
int mx[21][500005];
void Cmax(int l, int r, int v) {
    if (l > r) 
        return;
    int k = 63 - __builtin_clzll(r - l + 1);
    Cmax(mx[k][l], v), Cmax(mx[k][r - (1 << k) + 1], v);
}
int lst[2][500005];
signed main() {
    freopen("card.in", "r", stdin);
    freopen("card.out", "w", stdout);
    cin >> n >> S >> tp >> id;
    B = sqrt(S) + 1;
    for (int i = 0; i <= B; i++) {
        for (int j = 0; j <= B; j++) 
            g[i][j] = __gcd(i, j);
    }
    for (int i = 1; i <= n; i++) cin >> a[i], a[i] = min(a[i], S + 1);
    reverse(a + 1, a + n + 1), U::work(a, dp[1]), reverse(dp[1] + 1, dp[1] + n + 1);
    reverse(a + 1, a + n + 1), U::work(a, dp[0]);

    for (int i = 1; i <= n; i++) ans1 = max(ans1, dp[0][i]);
    if (!tp) return cout << ans1 << "\n", 0;
    for (int i = 1; i <= n; i++) {
        for (int j = pre[i - 1], c = 1; j && c <= 4; c++, j = pre[j - 1]) Cmax(j + 1, i - 1, dp[0][j] + dp[1][i] + F(a[j], a[i]));
        for (int j = nxt[i + 1], c = 1; j <= n && c <= 4; c++, j = nxt[j + 1]) Cmax(i + 1, j - 1, dp[0][i] + dp[1][j] + F(a[i], a[j]));
        if (a[i] > B) {
            for (int j = 0; j <= S; j += a[i]) {
                if (lst[0][j]) {
                    if (lst[1][j]) 
                        Cmax(lst[0][j], lst[0][j], dp[0][lst[1][j]] + dp[1][i] + j);
                    lst[1][j] = lst[0][j];
                    Cmax(lst[0][j] + 1, i - 1, dp[0][lst[0][j]] + dp[1][i] + j);
                }
                lst[0][j] = i;
            }
        }
    }
    for (int i = 19; ~i; i--) {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) 
            Cmax(mx[i][j], mx[i + 1][j]), Cmax(mx[i][j + (1 << i)], mx[i + 1][j]);
    }
    mx[0][1] = dp[1][2], mx[0][n] = dp[0][n - 1];
    for (int i = 1; i <= n; i++) ans2 ^= i * mx[0][i];
    cout << ans1 << " " << ans2 << "\n";
    return 0;
}

前缀 \(\max\) 信息,单侧递归线段树。

放缩。放缩贡献,放缩限制。考虑贡献对转移点的影响。

根号能过 \(5e5\)

posted @ 2025-08-08 21:54  forgotmyhandle  阅读(12)  评论(0)    收藏  举报