目录

2026“钉耙编程”中国大学生算法设计春季联赛 - 热身赛

1001 - 最大的公约数

题意

给定一个正整数 ,求两个正整数满足a+b=na+b=naba \ne b时,max{gcd(a,b)}max\{gcd(a,b)\}的值。

题解

可以发现max{gcd(a,b)}max\{gcd(a,b)\}一定是nn的一个因子,直接暴力n\sqrt n遍历即可,可以发现n,n/2(如果n%2==0)n,n/2(如果n\%2==0),是最大的两个约数,所以只需要找不是这两个数字的最大约数。

代码

#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int N = 32005;
void solve() {
    int n;
    scanf("%d", & n);
    int ans = 1;
    for (int i = 1; i * i <= n; i++) {
        if (n % i == 0) {
            if (i >= 3) ans = max(ans, n / i);
            if (n / i >= 3) ans = max(ans, i);
        }
    }
    printf("%d\n", ans);
}
int main() {
    int T;
    scanf("%d", & T);
    while (T--) solve();
    return 0;
}

1002 - 雪蜜冰城柠檬茶的神秘成分魔法

题意

21052*10^5个数字,范围在11~10710^7之间,10510^5次操作,操作有三种类型,1.将序列中的xx集体变成yy,2.序列末尾插入一个数字xx,3.删除数列中值为xx的数字,求最终序列。

题解

最开始想的是开10710^7个set,每个set[i]存储i所在的位置,如果你对set足够熟悉的话,你会知道两个set交换的时间复杂度是O(1)O(1)的(赛时从队友哪里知道的),即不依赖两个set的大小,只需要交换两个set的地址,clear的复杂度是O(n)O(n)的,即依赖于set的大小。

所以操作1只需要启发式地将小的集合合并到大的集合中,合并完以后再检查一下是否需要交换,需要的话直接换,然后把x所在的集合清空,操作2相当于直接在set中插入一个数字,操作3相当于直接删除掉一个set。

操作1和操作3,都需要删除操作比较耗时,但是据观察可以发现,全部数字的个数不会超过31053*10^5个,所以直接clear即可,操作1由于使用到了启发式方法,所以总的时间复杂度是O(nlog2n)O(nlog^2n),两个log一个是set插入操作带来的,一个是启发式操作带来的。

最终扫描全部的set,将所有数字按照位置排序后输出即可得到最终答案。

按照上面的思路完整坐下来会MLE,因为10710^7个set太占空间了,考虑优化,最终用到的数字不会超过31053*10^5个,可以使用map进行映射,因为映射空间是107310510^7\rightarrow 3*10^5,所以不用STL中的map,直接开10710^7大小的mp数组就可以,同时用一个iv数组记录反映射数组用来记录被映射的数字原来表示的是那个数字,这样操作总规模是31053*10^5级别的,就不用扫描到所有10710^7个set了。

代码

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    char ch = getchar();
    int x = 0, f = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return x * f;
}
set<int> s[300010];
int n, m, a[200010];
struct Query {
    int op, x, y;
}q[300010];
int mp[10000001], iv[300010];
int main() {
    int _ = read();
    while (_--) {
        n = read();
        int idx = 0;
        for (int i = 1; i <= n; i++) {
            a[i] = read();
            if (!mp[a[i]]) mp[a[i]] = ++idx, iv[idx] = a[i];
        }
        m = read();
        for (int i = 1; i <= m; i++) {
            q[i].op = read(), q[i].x = read();
            if (!mp[q[i].x]) mp[q[i].x] = ++idx, iv[idx] = q[i].x;
            if (q[i].op == 1) {
                q[i].y = read();
                if (!mp[q[i].y]) mp[q[i].y] = ++idx, iv[idx] = q[i].y;
            }
        }
        for (int i = 1; i <= n; i++) {
            s[mp[a[i]]].insert(i);
        }
        for (int i = 1; i <= m; i++) {
            if (q[i].op == 1) {
                if (q[i].x == q[i].y) continue;
                int x = mp[q[i].x], y = mp[q[i].y];
                if (s[x].size() > s[y].size()) {
                    for (auto & t: s[y]) s[x].insert(t);
                    s[y].swap(s[x]);
                    s[x].clear();
                } else {
                    for (auto & t: s[x]) s[y].insert(t);
                    s[x].clear();
                }
            } else if (q[i].op == 2) s[mp[q[i].x]].insert(++n);
            else s[mp[q[i].x]].clear();
        }
        vector < pair < int, int >> p;
        for (int i = 1; i <= idx; i++) {
            for (auto & t: s[i]) p.emplace_back(t, iv[i]);
            s[i].clear();
            mp[iv[i]] = 0;
            iv[i] = 0;
        }
        sort(p.begin(), p.end());
        if (p.size()) {
            printf("%d", p[0].second);
            for (int i = 1; i < p.size(); i++) printf(" %d", p[i].second);
        }
        puts("");
    }
    return 0;
}

1003 - 杂草蔓延

题意

给定一个 n × m 网格,初始时可任意选择一些格子长草。

之后每轮,所有与至少两个已长草格子四联通相邻的空格子,也会长草。

求最少初始长草格子数,使最终整个网格都能长满草。

题解

容易发现nnn*n的网格中只需要填充对角线就能覆盖所有格子,考虑m>nm>n的时候剩余n(mn)n*(m-n)个格子应该怎么填,可以发现如下图这样,空出一列来放一个杂草,可以将575*7范围内的格子填充满,所以可以得到总需要的杂草数量的公式为n+mn2(mn)n+\lfloor \frac{m-n}{2} \rfloor(m\ge n).

image-20260314211128851

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
    int _ = 1;
    cin >> _;
    while (_--) {
        ll n, m;
        cin >> n >> m;
        if (n < m) swap(n, m);
        cout << m + (n - m + 1) / 2 << '\n';
    }
    return 0;
}

1004 - 小z的开箱

题意

给定一个环上的 n 个数 a_i。你可以任选起点和方向走一圈,并任意选择吸收哪些数。

当前幸运值初始为 0,每次吸收一个数 x 后,新的幸运值变为 不超过原幸运值 + x 的最大质数,若不存在则变为 0

求最后能得到的最大幸运值。

数据范围: 1 ≤ T ≤ 101 ≤ n ≤ 10^51 ≤ a_i ≤ 10

题解

观察数据范围,打表可以发现,113后面的死一个质数是127,而127-113>10,所以最大幸运值只能为113,从每个非1的位置开始遍历一遍,如果记录p[s]为当前数字s跳到下一个位置需要的最大值,这个值可以提前预处理出来,因为总的质数个数很少,所以直接跑暴力就可以,后面要做的就是找到下一步应该跳到哪里,通过预处理ST表,可以在log(n)log(n)的时间内算出应该跳到哪一个位置,这个过程最多大约30步(1-113内质数的个数),最后记得将数组反向再处理一遍,总时间复杂度O(30nlogn)O(30nlogn)

代码

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    char ch = getchar();
    int x = 0, f = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return x * f;
}
bool isp(int n) {
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return 0;
    }
    return 1;
}
int p[130], a[200010];
int f[200010][19], lg[200010], jump[200];
int main() {
    int lst = 0;
    vector <int> pri;
    for (int i = 2; i <= 128; i++) {
        if (isp(i)) {
            pri.push_back(i);
            lst = i;
        }
        jump[i] = lst;
    }
    for (int i = 2; i <= 113; i++) {
        if (isp(i)) {
            p[i] = pri[upper_bound(pri.begin(), pri.end(), i) - pri.begin()] - i;
        }
    }
    for (int i = 2; i <= 200005; i++) {
        lg[i] = lg[i >> 1] + 1;
    }
    int _ = read();
    while (_--) {
        int n = read();
        for (int i = 1; i <= n; i++) a[i] = read();
        auto calc = [ & ]() -> int {
            for (int i = 1; i <= n; i++) a[i + n] = f[i][0] = f[i + n][0] = a[i];
            n *= 2;
            for (int j = 1; j < 19; j++) {
                for (int i = 1; i + (1 << j) - 1 <= n; i++) {
                    f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
                }
            }
            auto check = [ & ](int l, int r) -> int {
                int k = lg[r - l + 1];
                return max(f[l][k], f[r - (1 << k) + 1][k]);
            };
            int ans = 0;
            for (int i = 1; i <= n / 2; i++) {
                if (a[i] == 1) continue;
                int s = a[i];
                if (s >= 7) s = 7;
                else if (s >= 5) s = 5;
                else if (s >= 3) s = 3;
                else s = 2;
                int L = i, R = i + n / 2 - 1;
                for (int j = L + 1; j <= R;) {
                    if (check(j, R) < p[s]) break;
                    if (s == 113) break;
                    int l = j, r = R;
                    while (l < r) {
                        int mid = l + r >> 1;
                        if (check(j, mid) >= p[s]) r = mid;
                        else l = mid + 1;
                    }
                    s = jump[s + a[l]];
                    j = l + 1;
                }
                ans = max(ans, s);
                if (ans == 113) break;
            }
            n /= 2;
            return ans;
        };
        int ans = calc();
        if (ans != 113) {
            reverse(a + 1, a + 1 + n);
            ans = max(ans, calc());
        }
        printf("%d\n", ans);
    }
    return 0;
}

1006 - 光辉岁月

题意

给定一个长度为 nn0101 序列,第 ii 位为 11 表示这一年“记得”,为 00 表示这一年“忘记”。

接下来有 qq 次操作,每次给出一个区间 [l,r][l,r],表示将这段区间内的所有位置都变成 11,且这种修改是永久的。

对于每次操作后,需要求出一个最大的整数 kk,满足存在某个区间 [L,R][L,R],其中所有值为 11 的位置按从小到大排列后恰好构成一个长度为 kk 的等差数列。输出每次操作后的答案。

题解

考虑把所有当前为 11 的位置记下来,设这些位置为 p1,p2,,pmp_1,p_2,\dots,p_m。如果某个区间内所有为 11 的位置能构成等差数列,那么相邻两个位置的差一定全部相同。

于是定义一个辅助数组 ff

  • 若第 ii 位当前为 00,则 fi=0f_i=0
  • 若第 ii 位当前为 11,并且下一个为 11 的位置是 jj,则令 fi=jif_i=j-i
  • 若第 ii 位是最后一个为 11 的位置,则 fi=0f_i=0

这样一来,一个区间内所有为 11 的位置构成等差数列,当且仅当这个区间对应的 ff 数组中,去掉所有的 00 以后,剩下的数都相同。也就是说,问题转化成了维护“忽略 00 后最长连续相同数段”的长度。

每次把区间 [l,r][l,r] 变成 11 时,ff 数组只会在很少的位置发生变化:

  • 对于区间内部的所有位置 i[l,r1]i\in[l,r-1],因为它们的下一个为 11 的位置就是 i+1i+1,所以这些位置的 fif_i 都会变成 11
  • prepre 表示 ll 左边最近的一个为 11 的位置,那么它的下一个为 11 的位置会改成 ll,因此需要把 fpref_{pre} 改成 lprel-pre
  • nxtnxt 表示 rr 右边最近的一个为 11 的位置,那么位置 rr 的下一个为 11 的位置会变成 nxtnxt,因此需要把 frf_r 改成 nxtrnxt-r;如果右边不存在这样的点,则 fr=0f_r=0

于是每次操作本质上就是:

  • 一段区间赋值为 11
  • 两个单点修改

用一个 set / ODT 维护当前哪些位置已经是 11,从而快速找到 preprenxtnxt。再用线段树维护 ff 数组的信息:

  • lnum,lcnt:区间左端第一段非零数的值及长度
  • rnum,rcnt:区间右端最后一段非零数的值及长度
  • cnt:区间内非零数个数
  • ans:忽略 00 后,区间内最长连续相同非零段长度

这样合并两个儿子时,如果左儿子右端的非零值和右儿子左端的非零值相同,就可以把它们拼起来更新答案。

由于线段树里维护的是“相同公差段的边数”,而题目要求的是等差数列中的点数,所以每次输出答案时需要再加 11

时间复杂度为 O((n+q)logn)O((n+q)\log n)

代码

#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define eb emplace_back
#define mk make_pair
using namespace std;
using ll = long long;
using PII = pair < ll, ll > ;
const int N = 200010;
const ll INF = 1e9;
inline int read() {
    char ch = getchar();
    int x = 0, f = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return x * f;
}
struct ODT {
    struct node {
        int l, r;
        mutable ll v;
        node(int l, int r = -1, ll v = 0): l(l), r(r), v(v) {}
        bool operator < (const node & o) const {
            return l < o.l;
        }
    };
    set < node > s;
    ODT() {
        s.clear();
    }
    auto split(int pos) {
        auto it = s.lower_bound(node(pos));
        if (it != s.end() && it -> l == pos) return it;
        it--;
        int l = it -> l, r = it -> r;
        ll v = it -> v;
        s.erase(it);
        s.insert(node(l, pos - 1, v));
        return s.insert(node(pos, r, v)).first;
    }
    void assign(int l, int r, ll x) {
        auto itr = split(r + 1), itl = split(l);
        s.erase(itl, itr);
        s.insert(node(l, r, x));
    }
};
struct Node {
    int l, r;
    int lnum, lcnt, rnum, rcnt;
    int cnt, ans, tag;
}tr[N << 2];
int n, q, f[N], a[N];
ODT odt;
void pushup(Node & u, Node l, Node r) {
    if (l.cnt == 0 && r.cnt == 0) {
        u.lnum = u.lcnt = u.rnum = u.rcnt = u.cnt = u.ans = 0;
    } else if (l.cnt == 0) {
        u.lnum = r.lnum;
        u.lcnt = r.lcnt;
        u.rnum = r.rnum;
        u.rcnt = r.rcnt;
        u.cnt = r.cnt;
        u.ans = r.ans;
    } else if (r.cnt == 0) {
        u.lnum = l.lnum;
        u.lcnt = l.lcnt;
        u.rnum = l.rnum;
        u.rcnt = l.rcnt;
        u.cnt = l.cnt;
        u.ans = l.ans;
    } else {
        u.cnt = l.cnt + r.cnt;
        u.lnum = l.lnum, u.lcnt = l.lcnt;
        if (u.lcnt == l.cnt && l.rnum == r.lnum) u.lcnt = l.cnt + r.lcnt;
        u.rnum = r.rnum, u.rcnt = r.rcnt;
        if (r.rcnt == r.cnt && l.rnum == r.lnum) u.rcnt = r.cnt + l.rcnt;
        u.ans = max(l.ans, r.ans);
        if (l.rnum == r.lnum) u.ans = max(u.ans, l.rcnt + r.lcnt);
    }
    u.tag = 0;
}
void pushup(int u) {
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r) {
    tr[u]=Node({l,r,0,0,0,0,0,0,0});
    if(l==r){
        if(f[l]) tr[u]=Node({l,r,f[l],1,f[l],1,1,1,0});
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}
void pushdown(int u) {
    if (tr[u].tag) {
        tr[u << 1].lnum = tr[u << 1].rnum = tr[u << 1].tag = 1;
        tr[u << 1].lcnt = tr[u << 1].rcnt = tr[u << 1].cnt = tr[u << 1].ans = tr[u << 1].r - tr[u << 1].l + 1;
        tr[u << 1 | 1].lnum = tr[u << 1 | 1].rnum = tr[u << 1 | 1].tag = 1;
        tr[u << 1 | 1].lcnt = tr[u << 1 | 1].rcnt = tr[u << 1 | 1].cnt = tr[u << 1 | 1].ans = tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1;
        tr[u].tag = 0;
    }
}
void modify(int u, int l, int r, int x) {
    if (tr[u].l >= l && tr[u].r <= r) {
        tr[u].lnum = tr[u].rnum = tr[u].tag = 1;
        tr[u].lcnt = tr[u].rcnt = tr[u].cnt = tr[u].ans = tr[u].r - tr[u].l + 1;
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u << 1, l, r, x);
    if (r > mid) modify(u << 1 | 1, l, r, x);
    pushup(u);
}
void modify(int u, int x, int v) {
    if (tr[u].l == tr[u].r) {
        if(v)tr[u]=Node({tr[u].l,tr[u].r,v,1,v,1,1,1,0});
        else tr[u]=Node({tr[u].l,tr[u].r,0,0,0,0,0,0,0});
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (x <= mid) modify(u << 1, x, v);
    else modify(u << 1 | 1, x, v);
    pushup(u);
}
int main() {
    int _ = read();
    while (_--) {
        n = read(), q = read();
        vector < int > pos;
        odt = ODT();
        for (int i = 1; i <= n; i++) a[i] = read(), f[i] = 0;
        int st = 1;
        for (int i = 2; i <= n; i++) {
            if (a[i] ^ a[i - 1]) {
                odt.s.insert(ODT::node({st,i-1,a[st]}));
                st = i;
            }
        }
        odt.s.insert(ODT::node({st,n,a[st]}));
        odt.s.insert(ODT::node({n+1,n+1,0}));
        for (int i = 1; i <= n; i++)
            if (a[i]) pos.pb(i);
        for (int i = 0; i + 1 < pos.size(); i++) {
            f[pos[i]] = pos[i + 1] - pos[i];
        }
        if (pos.size()) f[pos.back()] = 0;
        build(1, 1, n);
        while (q--) {
            int l = read(), r = read();
            auto find_pre = [ & ](int pos) {
                auto it = odt.split(pos);
                while (it != odt.s.begin()) {
                    --it;
                    if (it -> l == n + 1) continue;
                    if (it -> v == 1) return it -> r;
                }
                return -1;
            };
            auto find_nxt = [ & ](int pos) {
                auto it = odt.split(pos + 1);
                while (it != odt.s.end()) {
                    if (it -> l == n + 1) return -1;
                    if (it -> v == 1) return it -> l;
                    ++it;
                }
                return -1;
            };
            int pre = find_pre(l);
            int nxt = find_nxt(r);
            odt.assign(l, r, 1);
            if (l <= r - 1) modify(1, l, r - 1, 1);
            if (pre != -1) modify(1, pre, l - pre);
            if (nxt != -1) modify(1, r, nxt - r);
            else modify(1, r, 0);
            printf("%d\n", tr[1].ans + 1);
        }
    }
    return 0;
}

1007 - 最大三段和

题意

给定序列 a,选三个互不相交的子段,且相邻两段之间至少隔一个数,其中第二段长度必须恰好为 1,求三段元素和的最大值。

题解

枚举中间长度为 1 的子段,答案为“左侧最大非空子段和 + 中间点 + 右侧最大非空子段和”,用前后缀预处理即可,复杂度 O(n)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int _ = 1;
    cin >> _;
    while (_--) {
        int n;
        cin >> n;
        vector<ll> a(n + 2), p(n + 2), s(n + 2), pm(n + 2), sm(n + 2), pmx(n + 2), smx(n + 2);
        for (int i = 1; i <= n; i++) cin >> a[i];
        pmx[0] = smx[n + 1] = -1e9;
        pm[0] = sm[n + 1] = -1e9;
        for (int i = 1; i <= n; i++) {
            p[i] = p[i - 1] + a[i];
            pmx[i] = max(pmx[i - 1], a[i]);
            if (p[i] <= 0) p[i] = 0;
            pm[i] = max(pm[i - 1], p[i]);
        }
        for (int i = n; i; i--) {
            s[i] = s[i + 1] + a[i];
            smx[i] = max(smx[i + 1], a[i]);
            if (s[i] <= 0) s[i] = 0;
            sm[i] = max(sm[i + 1], s[i]);
        }
        ll ans = a[1] + a[3] + a[5];
        for (int i = 3; i <= n - 2; i++) {
            ll sum = a[i] + (pmx[i - 2] >= 0 ? pm[i - 2] : pmx[i - 2]) + (smx[i + 2] >= 0 ? sm[i + 2] : smx[i + 2]);
            ans = max(ans, sum);
        }
        cout << ans << '\n';
    }
    return 0;
}

1008 - 单十一花钱计划

题意

nn件衣服,第ii件衣服有两种原价,分别为aia_ibib_i,进行qq次购买操作。对于第kk次购买,会给出一个区间[lk,rk][l_k,r_k],以及两种涨价幅度参数Xk,YkX_k,Y_k。设上一次购买得到的答案为 ansk1\mathrm{ans}_{k-1}(规定 ans0=0\mathrm{ans}_0=0),则本次真实涨价为:xk=Xkansk1,yk=Ykansk1.x_k = X_k \oplus \mathrm{ans}_{k-1},y_k = Y_k \oplus \mathrm{ans}_{k-1}.在本次购买中,区间 [lk,rk][l_k,r_k] 内的每件衣服价格都会临时变为:min(ai+xk,  bi+yk)\min(a_i + x_k,\; b_i + y_k)其中购买结束后价格恢复原状,不会对后续操作造成实际修改。

现在需要在区间 [lk,rk][l_k,r_k] 中选择一件衣服购买。 由于歪歪“有钱但不傻”,她会对每件衣服选择两种价格中较小的那一个; 但她又希望买到“最贵”的衣服,所以本次询问要求输出:maxi[lk,rk]min(ai+xk,  bi+yk)\max_{i \in [l_k,r_k]} \min(a_i + x_k,\; b_i + y_k),也就是:在当前询问给定的区间内,所有衣服按照“取两种价格较小者”后的最终价格中,最大的那个值,对于每次询问,输出对应答案。

题解

可以发现,对于一件固定的衣服,最终价格是min(ai+x, bi+y)\min(a_i+x,\ b_i+y).

ai+xbi+ya_i+x\le b_i+y 时取前者,否则取后者。整理可得:ai+xbi+y    xybiaia_i+x\le b_i+y \iff x-y\le b_i-a_i

也就是说,对于一次询问,真正起决定作用的只有固定的阈值 xyx-y

于是我们先把每件衣服按 biaib_i-a_i 从小到大排序。对于一个给定的 xyx-y,所有满足 biai<xyb_i-a_i<x-y 的衣服都会取 bi+yb_i+y,其余衣服都会取 ai+xa_i+x。这启发我们预处理出 nn 个版本的可持久化线段树:初始版本中所有位置都属于“取 ai+xa_i+x”这一类,线段树维护区间内这部分的最大 aia_i;然后按 biaib_i-a_i 从小到大依次把对应位置从 A 类切换到 B 类,也就是把该位置的贡献从 aia_i 改成 bib_i。因此第 kk 个版本就表示前 kk 个最小的 biaib_i-a_i 已经被划入“取 bi+yb_i+y”这一类。

查询时,先根据当前的 xyx-y 在排序后的数组中二分,找到最后一个满足 biai<xyb_i-a_i<x-y 的位置,对应选择合适的版本。然后在线段树上查询原区间 [l,r][l,r],得到当前区间内两类衣服的最大值:一类是仍取 ai+xa_i+x 的最大 aia_i,另一类是已切换为 bi+yb_i+y 的最大 bib_i。最后答案就是max(maxA+x, maxB+y)\max(\text{maxA}+x,\ \text{maxB}+y).

这样每次询问只需要一次二分和一次线段树查询,时间复杂度为 O(logn)O(\log n),总复杂度为 O((n+q)logn)O((n+q)\log n)

代码

#include <bits/stdc++.h>
#define x first
#define y second
#define eb emplace_back
#define mk make_pair
using namespace std;
using ll = long long;
using PII = pair < ll, ll > ;
const int N = 50010;
const ll INF = 1e9;
inline int read() {
    char ch = getchar();
    int x = 0, f = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return x * f;
}
struct Node {
    int l, r;
    ll mx1, mx2;
}tr[N << 5];
int n, m, a[N], b[N];
int root[N], tot;
int build(int l, int r) {
    int p = ++tot;
    if (l == r) {
        tr[p].mx1 = a[l], tr[p].mx2 = -INF;
        return p;
    }
    int mid = l + r >> 1;
    tr[p].l = build(l, mid);
    tr[p].r = build(mid + 1, r);
    tr[p].mx1 = max(tr[tr[p].l].mx1, tr[tr[p].r].mx1);
    tr[p].mx2 = max(tr[tr[p].l].mx2, tr[tr[p].r].mx2);
    return p;
}
int modify(int p, int l, int r, int x) {
    int q = ++tot;
    tr[q] = tr[p];
    if (l == r) {
        tr[q].mx1 = -1e9, tr[q].mx2 = b[x];
        return q;
    }
    int mid = l + r >> 1;
    if (x <= mid) tr[q].l = modify(tr[p].l, l, mid, x);
    else tr[q].r = modify(tr[p].r, mid + 1, r, x);
    tr[q].mx1 = max(tr[tr[q].l].mx1, tr[tr[q].r].mx1);
    tr[q].mx2 = max(tr[tr[q].l].mx2, tr[tr[q].r].mx2);
    return q;
}
pair < ll, ll > query(int p, int l, int r, int ql, int qr) {
    if (ql <= l && qr >= r) return mk(tr[p].mx1, tr[p].mx2);
    int mid = l + r >> 1;
    PII res1 = {-INF, -INF}, res2 = res1;
    if (ql <= mid) res1 = query(tr[p].l, l, mid, ql, qr);
    if (qr > mid) res2 = query(tr[p].r, mid + 1, r, ql, qr);
    return mk(max(res1.x, res2.x), max(res1.y, res2.y));
}
int main() {
    int _ = read();
    while (_--) {
        n = read(), m = read();
        vector < PII > p(n + 1);
        for (int i = 1; i <= n; i++) a[i] = read(), b[i] = read(), p[i] = mk(b[i] - a[i], i);
        sort(p.begin() + 1, p.end());
        tot = 0;
        root[0] = build(1, n);
        for (int i = 1; i <= n; i++) {
            root[i] = modify(root[i - 1], 1, n, p[i].y);
        }
        ll ans = 0, x = 0, y = 0;
        while (m--) {
            ll l = read(), r = read(), x = read() ^ ans, y = read() ^ ans;
            int rt = upper_bound(p.begin() + 1, p.end(), mk(x - y, n * 1 ll)) - p.begin() - 1;
            PII res = query(root[rt], 1, n, l, r);
            printf("%lld\n", ans = max(res.x + x, res.y + y));
        }
    }
    return 0;
}

1009 - 临渊羡鱼

题意

给定 n 条鱼的大小 a_i. 一张网能抓到所有大小在区间 [L,R] 内的鱼,网的大小定义为 R-L+1。求抓住全部鱼所需的最小网大小。

题解

签到题,直接输出max(a) - min(a) + 1.

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    int _ = 1;
    cin >> _;
    while (_--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (auto & t: a) cin >> t;
        sort(a.begin(), a.end());
        cout << a.back() - a[0] + 1 << '\n';
    }
    return 0;
}

1010 - 收买时间

题意

给定一个 n×mn × m 网格,每个格子 t[i][j] 表示最早可进入时间。你可以在已开放格子间免费移动,也可以花钱走单向传送门。要求总花费不超过 money,求最早什么时候能从 (1,1)(n,m)

题解

二分答案 T,判断在时间 T 时能否到达终点。

检查时把所有 t[i][j] <= T 的格子看作可用点,在这些点之间建图:

  • 相邻格子连一条费用 0 的边
  • 可用传送门连一条费用为其代价的有向边

然后从起点跑最短路,判断能否在总代价不超过 money 的情况下到达终点。

时间复杂度约为 O(logV(nm+k)log(nm))O(log V · (nm + k) log(nm))

代码

#include <bits/stdc++.h>
#define pb push_back
#define eb emplace_back
#define get(i, j)((i) * m + (j))
using namespace std;
using ll = long long;
using PII = pair<ll, int> ;
inline int read() {
    char ch = getchar();
    int x = 0, f = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return x * f;
}
int n, m, money, k;
struct Query {
    int x, y, p, q, w;
}q[200010];
vector<pair<int, int>> e[170000];
ll dis[170000];
int main() {
    int _ = read();
    while (_--) {
        n = read(), m = read(), money = read(), k = read();
        vector a(n, vector<int>(m));
        vector<int> tim;
        tim.reserve(n * m);
        for (auto & v: a) for (auto & t: v) t = read(), tim.pb(t);
        for (int i = 0; i < k; i++) q[i].x = read() - 1, q[i].y = read() - 1, q[i].p = read() - 1, q[i].q = read() - 1, q[i].w = read();
        sort(tim.begin(), tim.end());
        tim.erase(unique(tim.begin(), tim.end()), tim.end());
        auto add = [ & ](int a, int b) {
            e[a].eb(0, b), e[b].eb(0, a);
        };
        auto check = [ & ](int lim) -> bool {
            for (int i = 0; i < n * m; i++) e[i].clear(), dis[i] = 1e18;
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    if (a[i][j] > lim) continue;
                    if (i + 1 < n && a[i + 1][j] <= lim) add(get(i, j), get(i + 1, j));
                    if (j + 1 < m && a[i][j + 1] <= lim) add(get(i, j), get(i, j + 1));
                }
            }
            for (int i = 0; i < k; i++) {
                if (a[q[i].x][q[i].y] > lim || a[q[i].p][q[i].q] > lim) continue;
                e[get(q[i].x, q[i].y)].eb(q[i].w, get(q[i].p, q[i].q));
            }
            priority_queue < PII, vector < PII > , greater < PII >> h;
            h.push({0,0});
            dis[0] = 0;
            while (h.size()) {
                auto[d, v] = h.top();
                h.pop();
                if (d != dis[v]) continue;
                if (v + 1 == n * m) return d <= money;
                for (auto[w, to]: e[v]) {
                    if (dis[to] > d + w && d + w <= money) {
                        dis[to] = d + w;
                        h.push({dis[to],to});
                    }
                }
            }
            return 0;
        };
        int l = 0, r = tim.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (check(tim[mid])) r = mid;
            else l = mid + 1;
        }
        printf("%d\n", tim[l]);
    }
    return 0;
}
Comment seems to stuck. Try to refresh?✨