20250903

20250903 数据结构专题

T1

基站选址

直接线段树优化 dp 即可。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, K;
pair<int, int> a[20005];
int d[20005], c[20005], s[20005], w[20005];
int f[20005][105];
struct Segment_Tree {
    int mn[80005], tg[80005];
    void tag(int o, int v) { mn[o] += v, tg[o] += v; }
    void pushdown(int o) {
        if (!tg[o]) return;
        tag(o << 1, tg[o]);
        tag(o << 1 | 1, tg[o]);
        tg[o] = 0;
    }
    void pushup(int o) { mn[o] = min(mn[o << 1], mn[o << 1 | 1]); }
    void Build(int o, int l, int r) {
        mn[o] = inf;
        if (l == r) return;
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
    }
    void Change(int o, int l, int r, int x, int y) {
        if (l == r) return mn[o] = y, void();
        pushdown(o);
        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);
    }
    void Add(int o, int l, int r, int L, int R, int v) {
        if (L <= l && r <= R) return tag(o, v);
        pushdown(o);
        int mid = (l + r) >> 1;
        if (L <= mid) Add(o << 1, l, mid, L, R, v);
        if (R > mid) Add(o << 1 | 1, mid + 1, r, L, R, v);
        pushup(o);
    }
    int Query(int o, int l, int r, int L, int R) {
        if (L <= l && r <= R) return mn[o];
        pushdown(o);
        int mid = (l + r) >> 1;
        if (R <= mid) return Query(o << 1, l, mid, L, R);
        if (L > mid) return Query(o << 1 | 1, mid + 1, r, L, R);
        return min(Query(o << 1, l, mid, L, R), Query(o << 1 | 1, mid + 1, r, L, R));
    }
} seg[105];
signed main() {
    cin >> n >> K; ++K;
    for (int i = 2; i <= n; i++) cin >> d[i];
    for (int i = 1; i <= n; i++) cin >> c[i];
    for (int i = 1; i <= n; i++) cin >> s[i];
    for (int i = 1; i <= n; i++) cin >> w[i], a[i] = { d[i] + s[i] + 1, i };
    sort(a + 1, a + n + 1);
    for (int i = 0; i <= K; i++) seg[i].Build(1, 0, n);
    seg[0].Change(1, 0, n, 0, 0);
    d[n + 1] = inf;
    for (int i = 1, x = 1; i <= n + 1; i++) {
        while (x <= n && a[x].first <= d[i]) {
            int id = a[x].second, t = lower_bound(d + 1, d + n + 1, d[id] - s[id]) - d - 1;
            for (int j = 0; j <= K; j++) seg[j].Add(1, 0, n, 0, t, w[id]);
            ++x;
        }
        for (int j = 1; j <= min(i, K); j++) {
            f[i][j] = seg[j - 1].Query(1, 0, n, 0, i - 1) + c[i];
            if (i != n + 1) seg[j].Change(1, 0, n, i, f[i][j]);
        }
    }
    cout << *min_element(f[n + 1] + 1, f[n + 1] + K + 1) << "\n";
    return 0;
}

T2

护照

考虑拆成分别走到 \(1\)\(n\) 的最短路。发现在最优情况下,两条路径相交的一定是一段前缀。于是算出每个区间作为分叉点的代价,然后再最短路一遍即可。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, q;
int head[2000005], nxt[10000005], to[10000005], ecnt;
bool ew[10000005];
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
struct Segment_Tree {
    int id[800005], ncnt;
    void Build(int o, int l, int r) {
        if (l == r) return id[o] = l, void();
        id[o] = ++ncnt;
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
        add(id[o << 1], id[o], 0);
        add(id[o << 1 | 1], id[o], 0);
    }
    void Add(int o, int l, int r, int L, int R, int x) {
        if (L <= l && r <= R) return add(id[o], x, 1);
        int mid = (l + r) >> 1;
        if (L <= mid) Add(o << 1, l, mid, L, R, x);
        if (R > mid) Add(o << 1 | 1, mid + 1, r, L, R, x);
    }
} seg;
int dist[3][2000005];
bool vis[3][2000005];
struct node { int dis, x; };
bool operator<(node a, node b) { return a.dis > b.dis; }
priority_queue<node> Q;
void bfs(int _) {
    while (!Q.empty()) {
        node tmp = Q.top();
        Q.pop();
        int x = tmp.x;
        if (vis[_][x]) continue;
        vis[_][x] = 1;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (dist[_][v] > dist[_][x] + ew[i]) Q.push({ dist[_][v] = dist[_][x] + ew[i], v });
        }
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    seg.ncnt = n;
    seg.Build(1, 1, n);
    for (int i = 1, l, r; i <= n; i++) cin >> l >> r, seg.Add(1, 1, n, l, r, i);
    memset(dist, 63, sizeof dist);
    Q.push((node) { dist[0][1] = 0, 1 }), bfs(0);
    Q.push((node) { dist[1][n] = 0, n }), bfs(1);
    for (int i = 1; i <= n; i++) Q.push((node) { dist[2][i] = dist[0][i] + dist[1][i] - (i != 1 && i != n), i });
    bfs(2);
    cin >> q;
    while (q--) cin >> n, cout << (dist[2][n] >= inf ? -1 : dist[2][n]) << "\n";
    return 0;
}

T3

LCA

每个询问拆成两段前缀询问,离线加入每个点时将到根链 \(+1\),询问就询问一个点到根链权值和即可。

代码
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int p = 201314;
int n, q;
int head[100005], nxt[100005], to[100005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int top[50005], son[50005], dep[50005], dfn[50005], sz[50005], f[50005], ncnt;
void dfs1(int x, int fa, int d) {
    dep[x] = d;
    f[x] = fa;
    sz[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dfs1(v, x, d + 1);
            sz[x] += sz[v];
            if (sz[v] > sz[son[x]])
                son[x] = v;
        }
    }
}
void dfs2(int x, int t) {
    top[x] = t;
    dfn[x] = ++ncnt;
    if (!son[x])
        return;
    dfs2(son[x], t);
    for (int i = head[x]; i != 0; i = nxt[i]) {
        int v = to[i];
        if (v != f[x] && v != son[x])
            dfs2(v, v);
    }
}
struct _ {
    int p, z, id, v;
} arr[100005];
int ans[50005];
int sm[400005], tg[400005];
void tag(int o, int l, int r, int t) {
    sm[o] += (r - l + 1) * t;
    tg[o] += t;
}
void pushdown(int o, int l, int r) {
    int& t = tg[o];
    if (!t)
        return;
    int mid = (l + r) >> 1;
    tag(o << 1, l, mid, t);
    tag(o << 1 | 1, mid + 1, r, t);
    t = 0;
}
void Add(int o, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        tag(o, l, r, 1);
        return;
    }
    pushdown(o, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid)
        Add(o << 1, l, mid, L, R);
    if (R > mid)
        Add(o << 1 | 1, mid + 1, r, L, R);
    sm[o] = sm[o << 1] + sm[o << 1 | 1];
}
int Query(int o, int l, int r, int L, int R) {
    if (L <= l && r <= R)
        return sm[o];
    pushdown(o, l, r);
    int mid = (l + r) >> 1, ret = 0;
    if (L <= mid)
        ret += Query(o << 1, l, mid, L, R);
    if (R > mid)
        ret += Query(o << 1 | 1, mid + 1, r, L, R);
    return ret;
}
void Add_Path(int x) {
    while (x) {
        Add(1, 1, n, dfn[top[x]], dfn[x]);
        x = f[top[x]];
    }
}
int Query_Path(int x) {
    int ret = 0;
    while (x) {
        ret = (ret + Query(1, 1, n, dfn[top[x]], dfn[x])) % p;
        x = f[top[x]];
    }
    return ret;
}
int qcnt;
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q;
    for (int i = 2; i <= n; i++) {
        int x;
        cin >> x;
        add(x + 1, i);
    }
    dfs1(1, 0, 0);
    dfs2(1, 1);
    for (int i = 1; i <= q; i++) {
        int l, r, z;
        cin >> l >> r >> z;
        l++, r++, z++;
        arr[++qcnt] = (_){ l - 1, z, i, -1 };
        arr[++qcnt] = (_){ r, z, i, 1 };
    }
    sort(arr + 1, arr + qcnt + 1, [](_ a, _ b) { return a.p < b.p; });
    for (int i = 1, j = 1; i <= qcnt; i++) {
        while (j <= arr[i].p) Add_Path(j), j++;
        ans[arr[i].id] += (Query_Path(arr[i].z) * arr[i].v);
    }
    for (int i = 1; i <= q; i++) cout << (ans[i] + p) % p << "\n";
    return 0;
}

T4

三级跳

考虑 \(a, b\),发现如果 \([a, b]\) 中存在比两个端点都大的数,那么一定可以把其中一个端点移到那个大的数上去。于是只有这样的 \(a, b\) 对是有用的。而这样的 \(a, b\) 对实际上就是每个点左右第一个比它大的数的这些区间,一共只有 \(\mathcal{O}(n)\) 个。求出这些支配对之后,询问离线扫描线即可。

代码
#include <iostream>
#include <vector>
#define int long long
using namespace std;
#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 n, q;
int a[500005];
int stk[500005], sz;
int L[500005], R[500005];
vector<int> vec[500005];
vector<pair<int, int> > qs[500005];
int ans[500005];
struct Segment_Tree {
    int mx[2000005], mx2[2000005], tg[2000005];
    inline void tag(int o, int v) { tg[o] = max(tg[o], v), mx2[o] = max(mx2[o], tg[o] + mx[o]); }
    inline void pushdown(int o) { tag(o << 1, tg[o]), tag(o << 1 | 1, tg[o]); }
    void Build(int o, int l, int r) {
        if (l == r) 
            return mx[o] = a[l], void();
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
        mx[o] = max(mx[o << 1], mx[o << 1 | 1]);
        mx2[o] = mx[o];
    }
    void Cmax(int o, int l, int r, int L, int R, int v) {
        if (L <= l && r <= R) 
            return tag(o, v);
        pushdown(o);
        int mid = (l + r) >> 1;
        if (L <= mid) 
            Cmax(o << 1, l, mid, L, R, v);
        if (R > mid) 
            Cmax(o << 1 | 1, mid + 1, r, L, R, v);
        mx2[o] = max(mx2[o << 1], mx2[o << 1 | 1]);
    }
    int Query(int o, int l, int r, int L, int R) {
        if (L <= l && r <= R) 
            return mx2[o];
        pushdown(o);
        int mid = (l + r) >> 1;
        if (R <= mid) 
            return Query(o << 1, l, mid, L, R);
        if (L > mid) 
            return Query(o << 1 | 1, mid + 1, r, L, R);
        return max(Query(o << 1, l, mid, L, R), Query(o << 1 | 1, mid + 1, r, L, R));
    }
} seg;
signed main() {
    freopen("jump.in", "r", stdin);
    freopen("jump.out", "w", stdout);
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    seg.Build(1, 1, n);
    q = read();
    for (int i = 1; i <= n; i++) {
        while (sz && a[stk[sz]] < a[i]) --sz;
        L[i] = stk[sz]; stk[++sz] = i;
    }
    stk[sz = 0] = n + 1;
    for (int i = n; i; i--) {
        while (sz && a[stk[sz]] < a[i]) --sz;
        R[i] = stk[sz]; stk[++sz] = i;
    }
    for (int i = 1; i <= n; i++) {
        if (L[i]) 
            vec[L[i]].emplace_back(i);
        if (R[i] != n + 1) 
            vec[i].emplace_back(R[i]);
    }
    for (int i = 1; i <= q; i++) {
        int x = read(), y = read();
        qs[x].emplace_back(y, i);
    }
    for (int i = n; i; i--) {
        for (auto v : vec[i]) {
            if (v * 2 - i <= n) 
                seg.Cmax(1, 1, n, v * 2 - i, n, a[i] + a[v]);
        }
        for (auto v : qs[i]) ans[v.second] = seg.Query(1, 1, n, i, v.first);
    }
    for (int i = 1; i <= q; i++) cout << ans[i] << "\n";
    return 0;
}

T5

链上二次求和

\(S\) 为前缀和,\(S'\) 为二阶前缀和,则所求:\(\begin{equation}\begin{split}\sum\limits_{k = l}^r\sum\limits_{i = 1}^{n - k + 1}S_{i + k - 1} - S_{i - 1} &= \sum\limits_{k = l}^r S'_n - S'_{k - 1} - S'_{n - k} \\ &= (r - l + 1)S'_n - \sum\limits_{k = l - 1}^{r - 1} S'_{k} - \sum\limits_{k = n - r}^{n - l}S'_k \end{split}\end{equation}\)

只需要维护二阶前缀和的区间和。考虑修改对二阶前缀和的影响,容易列成区间加 \(ai^2 + bi + c\) 的形式,其中 \(i\) 为下标。直接线段树维护即可。

代码
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
#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);
}
const int P = 1000000007, i2 = (P + 1) / 2;
inline void Madd(int &x, int y) { (x += y) >= P ? (x -= P) : 0; }
inline int Msum(int x, int y) { return Madd(x, y), x; }
int n, q, pre[200005];
struct Tag {
    int x, y, z;
    void operator+=(Tag t) { Madd(x, t.x), Madd(y, t.y), Madd(z, t.z); }
};
int a[200005];
struct Segment_Tree {
    int s[800005];
    int s1[800005], s2[800005], s3[800005];
    Tag tg[800005];
    void tag(int o, Tag t) {
        tg[o] += t;
        Madd(s[o], (s3[o] * t.x + s2[o] * t.y + s1[o] * t.z) % P);
    }
    void pushdown(int o) {
        if (tg[o].x == 0 && tg[o].y == 0 && tg[o].z == 0) return;
        tag(o << 1, tg[o]);
        tag(o << 1 | 1, tg[o]);
        tg[o] = (Tag) { 0, 0, 0 };
    }
    void Build(int o, int l, int r) {
        if (l == r) return s[o] = a[l], s1[o] = 1, s2[o] = l, s3[o] = l * r % P, void();
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
        s[o] = (s[o << 1] + s[o << 1 | 1]) % P;
        s1[o] = s1[o << 1] + s1[o << 1 | 1];
        s2[o] = (s2[o << 1] + s2[o << 1 | 1]) % P;
        s3[o] = (s3[o << 1] + s3[o << 1 | 1]) % P;
    }
    void Add(int o, int l, int r, int L, int R, int x, int y, int z) {
        if (L <= l && r <= R) return tag(o, { x, y, z });
        pushdown(o);
        int mid = (l + r) >> 1;
        if (L <= mid) Add(o << 1, l, mid, L, R, x, y, z);
        if (R > mid) Add(o << 1 | 1, mid + 1, r, L, R, x, y, z);
        s[o] = (s[o << 1] + s[o << 1 | 1]) % P;
    }
    int Query(int o, int l, int r, int L, int R) {
        if (L > R) return 0;
        if (L <= l && r <= R) return s[o];
        pushdown(o);
        int mid = (l + r) >> 1;
        if (R <= mid) return Query(o << 1, l, mid, L, R);
        if (L > mid) return Query(o << 1 | 1, mid + 1, r, L, R);
        return (Query(o << 1, l, mid, L, R) + Query(o << 1 | 1, mid + 1, r, L, R)) % P;
    }
} seg;
signed main() {
    n = read(), q = read();
    for (int i = 1; i <= n; i++) a[i] = Msum(a[i - 1], read());
    for (int i = 1; i <= n; i++) Madd(a[i], a[i - 1]);
    seg.Build(1, 1, n); 
    while (q--) {
        int op = read(), l = read(), r = read(), x;
        if (op == 1) {
            x = read();
            if (l > r) swap(l, r);
            seg.Add(1, 1, n, l, r, i2 * x % P, (P - (P + l * 2 - 3) * i2 % P) * x % P, (l - 1) * (P + l - 2) % P * i2 % P * x % P);
            r != n ? seg.Add(1, 1, n, r + 1, n, 0, (r - l + 1) * x % P, ((r - l + 1) * (r - l + 2) % P * i2 % P + P - r * (r - l + 1) % P) % P * x % P) : void();
        } else {
            int s = (r - l + 1) * seg.Query(1, 1, n, n, n) - seg.Query(1, 1, n, max(1ll, l - 1), r - 1) - seg.Query(1, 1, n, max(1ll, n - r), n - l);
            cout << (s + P * 2) % P << "\n";
        }
    }
    return 0;
}

T6

树状数组

显然当 \(l \neq 1\) 时是询问 \(a_{l - 1}\)\(a_r\) 相等概率,否则是询问 \(r\) 位置的前后缀和相等的概率。第一问,考虑所有修改区间,若只包含 \(l - 1\),那么以 \(\frac1{len}\) 的概率改变这个询问的答案;只包含右端点也同理。若同时包含两个,那么以 \(\frac2{len}\) 的概率改变这个询问的答案。这部分相当于三维偏序,树套树或者 CDQ 维护。第二问,所有不包含 \(r\) 的区间修改必然改变这个询问的答案,否则以 \(\frac{len - 1}{len}\) 的概率改变区间答案。这部分相当于二维偏序,直接维护即可。

以下代码过不了洛谷数据,因为有一个地方没有考虑逆元的不存在性。但它能过原题数据,我就懒得改了。

代码
#include <iostream>
#include <valarray>
#include <string.h>
#define lowbit(x) ((x) & (-(x)))
#define int long long
using namespace std;
const int P = 998244353, i2 = (P + 1) / 2;
inline void Madd(int &x, int y) { (x += y) >= P ? (x -= P) : 0; }
inline int Msum(int x, int y) { return Madd(x, y), x; }
int n, q;
valarray<int> qpow(valarray<int> x, int y = P - 2) {
    valarray<int> ret = { 1, 1 };
    while (y) {
        if (y & 1) 
            ret = ret * x % P;
        y >>= 1;
        x = x * x % P;
    }
    return ret;
}
void FWT(valarray<int> &p, int t = 1) {
    valarray<int> ret = { Msum(p[0], p[1]), Msum(p[0], P - p[1]) };
    if (t != 1) ret = ret * i2 % P;
    p = ret;
}
bool fl[100005];
struct Q { int op, l, r, id; valarray<int> v; } qs[100005];
struct Segment_Tree {
    valarray<int> T[400005], tg[400005];
    void Build(int o, int l, int r) {
        T[o] = tg[o] = { 1, 1 };
        if (l == r) return;
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
    }
    void Add(int o, int l, int r, int L, int R, valarray<int> v) {
        if (L <= l && r <= R) return tg[o] = tg[o] * v % P, void();
        int mid = (l + r) >> 1;
        if (L <= mid) Add(o << 1, l, mid, L, R, v);
        if (R > mid) Add(o << 1 | 1, mid + 1, r, L, R, v);
    }
    valarray<int> Query(int o, int l, int r, int x) {
        if (l == r) return tg[o];
        int mid = (l + r) >> 1;
        if (x <= mid) return tg[o] * Query(o << 1, l, mid, x) % P;
        else return tg[o] * Query(o << 1 | 1, mid + 1, r, x) % P;
    }
} seg, seg2;
struct BIT {
    valarray<int> bit[100005];
    void init(int x) { for (; x; --x) bit[x] = { 1, 1 }; }
    void Re(int x) { for (; x <= n; x += lowbit(x)) bit[x] = { 1, 1 }; }
    void add(int x, valarray<int> y) { for (; x <= n; x += lowbit(x)) bit[x] = bit[x] * y % P; }
    valarray<int> query(int x) {
        valarray<int> ret = { 1, 1 };
        for (; x; x -= lowbit(x)) ret = ret * bit[x] % P;
        return ret;
    }
} bit;
valarray<int> X;
valarray<int> ans[100005];
void Solve(int l, int r) {
    if (l >= r) return;
    int mid = (l + r) >> 1;
    Solve(l, mid), Solve(mid + 1, r);
    sort(qs + l, qs + mid + 1, [](Q x, Q y) { return x.l < y.l; });
    sort(qs + mid + 1, qs + r + 1, [](Q x, Q y) { return x.l < y.l; });
    int j = l;
    for (int i = mid + 1; i <= r; i++) {
        if (qs[i].op == 1) continue;
        for (; j <= mid && qs[j].l <= qs[i].l; j++) qs[j].op == 1 ? bit.add(n - qs[j].r + 1, qs[j].v) : void();
        ans[qs[i].id] = ans[qs[i].id] * bit.query(n - qs[i].r + 1) % P;
    }
    for (int i = l; i < j; i++) qs[i].op == 1 ? bit.Re(n - qs[i].r + 1) : void();
}
signed main() {
    freopen("bit.in", "r", stdin);
    freopen("bit.out", "w", stdout);
    X = { 0, 1 }; FWT(X);
    cin >> n >> q;
    seg.Build(1, 1, n); seg2.Build(1, 1, n);
    for (int i = 1; i <= q; i++) {
        cin >> qs[i].op >> qs[i].l >> qs[i].r, qs[i].id = i;
        if (qs[i].op == 2) {
            --qs[i].l;
            if (qs[i].l == 0) ans[i] = seg2.Query(1, 1, n, qs[i].r);
            else {
                ans[i] = { 1, 1 };
                ans[i] = ans[i] * seg.Query(1, 1, n, qs[i].l) % P * seg.Query(1, 1, n, qs[i].r) % P;
            }
        } else {
            int ilen = qpow({ qs[i].r - qs[i].l + 1, 0 }, P - 2)[0];
            valarray<int> tmp = { P + 1 - ilen, ilen }, tmp2; FWT(tmp); tmp2 = qpow(tmp, 2 * (P - 2));
            seg.Add(1, 1, n, qs[i].l, qs[i].r, tmp);
            if (qs[i].l != qs[i].r) tmp = { (P * 2 + 1 - 2 * ilen) % P, 2 * ilen % P }, FWT(tmp), qs[i].v = tmp * tmp2 % P;
            else qs[i].v = { 1, 1 };
            if (qs[i].l != 1) seg2.Add(1, 1, n, 1, qs[i].l - 1, X);
            if (qs[i].r != n) seg2.Add(1, 1, n, qs[i].r + 1, n, X);
            if (qs[i].l != qs[i].r) tmp = { ilen, P + 1 - ilen }, FWT(tmp), seg2.Add(1, 1, n, qs[i].l, qs[i].r, tmp);
        }
    }
    bit.init(n);
    Solve(1, q);
    sort(qs + 1, qs + q + 1, [](Q x, Q y) { return x.id < y.id; });
    for (int i = 1; i <= q; i++) if (qs[i].op == 2) FWT(ans[i], -1), cout << ans[i][0] << "\n";
    return 0;
}

T7

治疗计划

考虑把过程画在图上,发现计划之间实际上是相互衔接的关系。而我们要找的是一些方案,使得它们相互衔接,并且从左端点一直走到右端点。因此我们直接考虑最短路,每个计划和能衔接它(或它能衔接)的计划建边,然后求 \(1 \rightarrow n\) 的最短路。由于对于每个计划,其他计划中 \(t\) 比它大和比它小的计划和它是否衔接的判定不同,因此要开两棵线段树。如果直接使用线段树优化建图的话需要可持久化,但由于这是点权最短路,而点权最短路具有的性质是每个点第一次被扩展到的时候就是最短路。因此可以直接开两棵线段树维护每个 \(r - t\)\(r + t\) 上的计划,然后每次扩展直接到线段树上找扩展到的点。这样就是单 \(\log\) 了。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n, m;
int head[10000005], nxt[10000005], to[10000005], ew[10000005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int od[100005], val[100005], c[100005], _od[100005];
int cnt;
struct Segment_Tree {
    int ncnt;
    struct node { int l, r, x; } T[10000005];
    void Insert(int &p, int q, int l, int r, int x) {
        T[p = ++ncnt] = T[q];
        if (l == r) return T[p].x = od[l], void();
        T[p].x = ++cnt;
        int mid = (l + r) >> 1;
        if (x <= mid) Insert(T[p].l, T[p].l, l, mid, x);
        else Insert(T[p].r, T[q].r, mid + 1, r, x);
        if (T[p].l) add(T[T[p].l].x, T[p].x, 0);
        if (T[p].r) add(T[T[p].r].x, T[p].x, 0);
    }
    void Add(int o, int l, int r, int L, int R, int x) {
        if (!o || L > R) return;
        if (L <= l && r <= R) return add(T[o].x, x, c[x]);
        int mid = (l + r) >> 1;
        if (L <= mid) Add(T[o].l, l, mid, L, R, x);
        if (R > mid) Add(T[o].r, mid + 1, r, L, R, x);
    }
} seg;
int dist[10000005];
bool vis[10000005];
struct node { int dis, x; };
bool operator<(node a, node b) { return a.dis > b.dis; }
priority_queue<node> Q;
void dijkstra() {
    while (!Q.empty()) {
        node tmp = Q.top();
        Q.pop();
        int x = tmp.x;
        if (vis[x]) continue;
        vis[x] = 1;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (dist[v] > dist[x] + ew[i]) Q.push({ dist[v] = dist[x] + ew[i], v });
        }
    }
}
int t[100005], l[100005], r[100005];
int rt[100005];
int o[100005];
signed main() {
    freopen("treatment.in", "r", stdin);
    freopen("treatment.out", "w", stdout);
    memset(dist, 63, sizeof dist);
    cin >> n >> m; cnt = m;
    for (int i = 1; i <= m; i++) cin >> t[i] >> l[i] >> r[i] >> c[i], o[i] = od[i] = i;
    sort(od + 1, od + m + 1, [](int x, int y) { return r[x] - t[x] < r[y] - t[y]; });
    sort(o + 1, o + m + 1, [](int x, int y) { return t[x] > t[y]; });
    for (int i = 1; i <= m; i++) val[i] = r[od[i]] - t[od[i]], _od[od[i]] = i;
    for (int i = 1; i <= m; i++) {
        int x = o[i];
        int tl = lower_bound(val + 1, val + m + 1, l[x] - t[x] - 1) - val, tr = upper_bound(val + 1, val + m + 1, r[x] - t[x] + 1) - val - 1;
        seg.Insert(rt[i], rt[i - 1], 1, m, _od[x]);
        seg.Add(rt[i], 1, m, tl, tr, x);
        if (l[x] == 1) Q.push((node) { dist[x] = c[x], x });
    }
    sort(od + 1, od + m + 1, [](int x, int y) { return r[x] + t[x] < r[y] + t[y]; });
    reverse(o + 1, o + m + 1);
    for (int i = 1; i <= m; i++) val[i] = r[od[i]] + t[od[i]], _od[od[i]] = i;
    seg.ncnt = 0;
    for (int i = 1; i <= m; i++) {
        int x = o[i];
        int tl = lower_bound(val + 1, val + m + 1, l[x] + t[x] - 1) - val, tr = upper_bound(val + 1, val + m + 1, r[x] + t[x] + 1) - val - 1;
        seg.Insert(rt[i], rt[i - 1], 1, m, _od[x]);
        seg.Add(rt[i], 1, m, tl, tr, x);
    }
    dijkstra();
    int ans = inf;
    for (int i = 1; i <= m; i++) if (r[i] == n) ans = min(ans, dist[i]);
    cout << (ans == inf ? -1 : ans) << "\n";
    return 0;
}

T9

Orchestra

固定上边界,枚举下边界,尝试对每个右边界维护答案。显然每次加入一个点只会影响 \(\mathcal{O}(K)\) 个本质不同的右边界的答案。链表维护所有有值的列即可快速更新影响到的右边界的答案。于是做完了。有一个就是链表不好维护加入,于是倒着删除就可以了。也是同样维护。

代码
#include <iostream>
#include <vector>
#define int long long
using namespace std;
int N, M, n, K, ans;
pair<int, int> p[3005];
int pre[3005], nxt[3005];
int cnt[3005], curl[3005], curc[3005];
vector<int> vec[3005];
signed main() {
    cin >> N >> M >> n >> K;
    for (int i = 1; i <= n; i++) cin >> p[i].first >> p[i].second, vec[p[i].first].emplace_back(p[i].second);
    for (int i = 1; i <= N; i++) {
        nxt[0] = 1, pre[M + 1] = M;
        for (int j = 1; j <= M; j++) pre[j] = j - 1, nxt[j] = j + 1, cnt[j] = 0;
        for (int j = i; j <= N; j++) for (auto v : vec[j]) ++cnt[v];
        auto del = [&](int x) { pre[nxt[x]] = pre[x], nxt[pre[x]] = nxt[x]; };
        for (int j = 1; j <= M; j++) !cnt[j] ? del(j) : void();
        for (int j = 1, c = 0; j <= M; j++) for (c += cnt[j], curl[j] = curl[j - 1], curc[j] = c; c - cnt[curl[j]] >= K; c -= cnt[curl[j]], ++curl[j], curc[j] = c);
        int csum = 0;
        for (int j = nxt[0]; j != M + 1; j = nxt[j]) csum += (nxt[j] - j) * curl[j];
        for (int j = N; j >= i; j--) {
            ans += csum;
            for (int v : vec[j]) {
                --cnt[v];
                if (!cnt[v]) del(v);
                for (int k = v; k != M + 1 && curl[k] <= v; k = nxt[k]) {
                    --curc[k];
                    csum -= (nxt[k] - k) * curl[k];
                    while (curl[k] && curc[k] < K) curl[k] = pre[curl[k]], curc[k] += cnt[curl[k]];
                    csum += (nxt[k] - k) * curl[k];
                }
            }
        }
    }
    cout << ans << "\n";
    return 0;
}

T10

蚂蚁与方糖

考虑霍尔定理推论,设左部点集 \(S\),对每个左部点集 \(T\) 定义 \(N(T)\) 表示其右部点集中的邻域。则最大匹配即为 \(|S| - \max\limits_{T \subseteq S} \{|T| - |N(T)|\}\)。因此即为选择一些区间 \([l_i, r_i]\),最大化所有区间中蚂蚁个数和减去 \(\bigcup [l_i - L, r_i + L]\) 的方糖个数。于是对每个区间维护 \(ans_{0 / 1, 0 / 1}\) 表示区间左右端点选或不选的答案,合并时对于中间两个端点都选的情况再补上一个 \([mid + 1 - L, mid - 1 + L]\) 的方糖数量。这个也额外维护 \(w\) 表示。加蚂蚁就直接加。加方糖的话,我们考虑实际上我们要影响的是所有 \([x - L, x + L]\) 的叶子,于是找到这些区间,注意到这些区间,只要选了至少一个蚂蚁,就要把答案减掉增加的方糖数量。然后再打上 tag 表示儿子也要改。而还有区间不选蚂蚁的情况,就再把 \(ans_{0, 0}\)\(0\)\(\max\)。而对于 \(w\) 的修改,考虑受影响的东西要么是递归找到的区间的祖先,要么是这些区间的子树。前一种情况在 pushup 之前更新,后一种也是打 tag 搞掉。然后直接做就做完了,不需要任何离散化。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define int long long
using namespace std;
const int N = 1000000000, inf = 0x3f3f3f3f3f3f3f3f;
inline void Cmax(int &x, int y) { x = max(x, y); }
int q, L, s;
signed rt;
struct node {
    signed l, r;
    int ans[2][2];
    int tg, s;
} T[18000005];
struct Segment_Tree {
    signed ncnt;
    void tag(int o, int v) {
        T[o].ans[0][0] = max(T[o].ans[0][0] - v, 0ll);
        T[o].ans[0][1] -= v;
        T[o].ans[1][0] -= v;
        T[o].ans[1][1] -= v;
        T[o].tg += v, T[o].s += v;
    }
    void pushdown(int o) {
        if (!T[o].tg) return;
        if (!T[o].l) T[o].l = ++ncnt;
        if (!T[o].r) T[o].r = ++ncnt;
        tag(T[o].l, T[o].tg);
        tag(T[o].r, T[o].tg);
        T[o].tg = 0;
    }
    void pushup(int o) {
        memset(T[o].ans, -63, sizeof T[o].ans);
        for (int x : { 0, 1 }) {
            for (int y : { 0, 1 }) {
                for (int a : { 0, 1 }) {
                    for (int b : { 0, 1 }) 
                        Cmax(T[o].ans[x][b], T[T[o].l].ans[x][y] + T[T[o].r].ans[a][b] + (y && a) * T[o].s);
                }
            }
        }
    }
    void Add1(signed &o, int l, int r, int x, int y) {
        if (!o) o = ++ncnt;
        if (l == r) return T[o].ans[1][1] += y, void();
        pushdown(o);
        int mid = (l + r) >> 1;
        if (x <= mid) Add1(T[o].l, l, mid, x, y);
        else Add1(T[o].r, mid + 1, r, x, y);
        pushup(o);
    }
    void Add2(signed &o, int l, int r, int L, int R, int v) {
        if (!o) o = ++ncnt;
        if (L <= l && r <= R) return tag(o, v);
        pushdown(o);
        int mid = (l + r) >> 1;
        if (L <= mid) Add2(T[o].l, l, mid, L, R, v);
        if (R > mid) Add2(T[o].r, mid + 1, r, L, R, v);
        if (L <= mid && mid < R) T[o].s += v;
        pushup(o);
    }
} seg;
signed main() {
    cin >> q >> L;
    while (q--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1) seg.Add1(rt, 0, N, x, y), s += y;
        else seg.Add2(rt, 0, N, max(0ll, x - L), min(N, x + L), y);
        cout << s - max({ T[rt].ans[0][0], T[rt].ans[0][1], T[rt].ans[1][0], T[rt].ans[1][1] }) << "\n";
    }
    return 0;
}

T11

Number of Components

显然连通块是区间,一个位置被切开等价于它前面的 \(\min\) 比它后面的 \(\max\) 严格大。然后考虑对每个值考虑,发现如果对一个数,把 \(\ge\) 它的设为 \(1\),其他的设为 \(0\),则一个数合法当且仅当它对应的东西前面全是 \(1\),后面全是 \(0\)。于是只需要对每个值维护连续 \(10\) 对的数量即可。这个每次修改的时候考虑和相邻东西的贡献即可。然后就是要求序列里有多少个 \(1\),这个就直接维护区间最小值及其个数即可。

代码
#include <iostream>
using namespace std;
const int N = 1000001;
int n, q;
struct node { int mn, cnt; } T[4000005];
node operator+(node a, node b) { int x = min(a.mn, b.mn); return { x, a.cnt * (x == a.mn) + b.cnt * (x == b.mn) }; }
struct Segment_Tree {
    int tg[4000005];
    void tag(int o, int v) { tg[o] += v, T[o].mn += v; }
    void pushdown(int o) {
        if (!tg[o]) return;
        tag(o << 1, tg[o]);
        tag(o << 1 | 1, tg[o]);
        tg[o] = 0;
    }
    void Opt(int o, int l, int r, int x) {
        if (l == r) return T[o].cnt ^= 1, void();
        pushdown(o);
        int mid = (l + r) >> 1;
        if (x <= mid) Opt(o << 1, l, mid, x);
        else Opt(o << 1 | 1, mid + 1, r, x);
        T[o] = T[o << 1] + T[o << 1 | 1];
    }
    void Add(int o, int l, int r, int L, int R, int v) {
        if (L <= l && r <= R) return tag(o, v);
        pushdown(o);
        int mid = (l + r) >> 1;
        if (L <= mid) Add(o << 1, l, mid, L, R, v);
        if (R > mid) Add(o << 1 | 1, mid + 1, r, L, R, v);
        T[o] = T[o << 1] + T[o << 1 | 1];
    }
} seg;
int a[500005];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q;
    a[0] = N, a[n + 1] = 0;
    for (int i = 1; i <= n + 1; i++) {
        if (i <= n) cin >> a[i], seg.Opt(1, 1, N, a[i]);
        if (a[i] < a[i - 1]) seg.Add(1, 1, N, a[i] + 1, a[i - 1], 1);
    }
    while (q--) {
        int x, y;
        cin >> x >> y;
        seg.Opt(1, 1, N, a[x]);
        if (a[x - 1] > a[x]) seg.Add(1, 1, N, a[x] + 1, a[x - 1], -1);
        if (a[x] > a[x + 1]) seg.Add(1, 1, N, a[x + 1] + 1, a[x], -1);
        a[x] = y;
        seg.Opt(1, 1, N, y);
        if (a[x - 1] > a[x]) seg.Add(1, 1, N, a[x] + 1, a[x - 1], 1);
        if (a[x] > a[x + 1]) seg.Add(1, 1, N, a[x + 1] + 1, a[x], 1);
        node tmp = T[1];
        if (tmp.mn == 1) cout << tmp.cnt << "\n";
        else cout << "0\n";
    }
    return 0;
}

T12

鱼 2

考虑一条鱼什么时候合法,一定是向左向右扩展到极限之后能扩展完原序列。我们直接考虑线段树维护,那么需要合并两个区间的答案。两个区间内有用的东西就是扩展到左端点或右端点或扩展完整个区间的那些东西。那么考虑扩展到左端点的鱼的数量,发现本质不同的只有 \(\log nV\) 种,因为每出现一种本质不同的会让总大小至少翻倍。于是合并时直接把两边的这些东西双指针合并一下即可。

代码
#include <iostream>
#include <string.h>
#include <cassert>
#define int long long
using namespace std;
int n, q;
int A[100005];
struct node {
    int l, r, ans, sum;
    signed p[70], s[70];
    int ps[70], ss[70];
    signed pc[70], sc[70];
} T[400005];
int r1[100005], r2[100005];
node operator+(node a, node b) {
    node c; c.sum = a.sum + b.sum;
    c.l = a.l, c.r = b.r; c.ans = 0;
    memcpy(c.p, a.p, sizeof a.p);
    memcpy(c.ps, a.ps, sizeof a.ps);
    memcpy(c.pc, a.pc, sizeof a.pc);
    memcpy(c.s, b.s, sizeof b.s);
    memcpy(c.ss, b.ss, sizeof b.ss);
    memcpy(c.sc, b.sc, sizeof b.sc);
    int x = a.s[0], y = b.p[0]; a.s[0] = a.r + 1, b.p[0] = b.l - 1;
    a.ss[x + 1] = a.sum, b.ps[y + 1] = b.sum;
    int j = 1, p1 = -1, v1 = 0, s1 = 0, p2 = -1, v2 = 0, s2 = 0;
    if (a.sum < A[b.l]) ++c.p[0], c.p[c.p[0]] = a.r, c.ps[c.p[0]] = a.sum, c.pc[c.p[0]] = a.ans;
    for (int i = 1; i <= y; i++) {
        while (j <= x + 1 && b.ps[i] + a.ss[j - 1] >= A[a.s[j - 1] - 1]) ++j;
        if (b.ps[i] + a.ss[j - 1] >= A[b.p[i] + 1]) b.pc[i + 1] += b.pc[i];
        else if (j == x + 2) ++c.p[0], c.p[c.p[0]] = b.p[i], c.ps[c.p[0]] = b.ps[i] + a.sum, c.pc[c.p[0]] = b.pc[i];
    } b.ans += b.pc[y + 1];
    while (j <= x + 1 && b.sum + a.ss[j - 1] >= A[a.s[j - 1] - 1]) ++j;
    if (j > x + 1) c.ans += b.ans;
    else if (j != 1) p1 = a.s[j - 1], v1 = b.ans, s1 = a.ss[j - 1];

    if (b.sum < A[a.r]) ++c.s[0], c.s[c.s[0]] = b.l, c.ss[c.s[0]] = b.sum, c.sc[c.s[0]] = b.ans;
    for (int i = j = 1; i <= x; i++) {
        while (j <= y + 1 && a.ss[i] + b.ps[j - 1] >= A[b.p[j - 1] + 1]) ++j;
        if (a.ss[i] + b.ps[j - 1] >= A[a.s[i] - 1]) a.sc[i + 1] += a.sc[i];
        else if (j == y + 2) ++c.s[0], c.s[c.s[0]] = a.s[i], c.ss[c.s[0]] = a.ss[i] + b.sum, c.sc[c.s[0]] = a.sc[i];
    } a.ans += a.sc[x + 1];
    while (j <= y + 1 && a.sum + b.ps[j - 1] >= A[b.p[j - 1] + 1]) ++j;
    if (j > y + 1) c.ans += a.ans;
    else if (j != 1) p2 = b.p[j - 1], v2 = a.ans, s2 = b.ps[j - 1];

    for (int i = 1; i <= c.p[0]; i++) if (c.p[i] == p2) c.pc[i] += v2, p2 = -1;
    if (p2 != -1) {
        for (int i = c.p[0]; ~i; i--) {
            if (i && c.p[i] > p2) c.p[i + 1] = c.p[i], c.ps[i + 1] = c.ps[i], c.pc[i + 1] = c.pc[i];
            else { c.p[i + 1] = p2, c.ps[i + 1] = a.sum + s2, c.pc[i + 1] = v2; break; }
        }
        ++c.p[0];
    }
    for (int i = 1; i <= c.s[0]; i++) if (c.s[i] == p1) c.sc[i] += v1, p1 = -1;
    if (p1 != -1) {
        for (int i = c.s[0]; ~i; i--) {
            if (i && c.s[i] < p1) c.s[i + 1] = c.s[i], c.ss[i + 1] = c.ss[i], c.sc[i + 1] = c.sc[i];
            else { c.s[i + 1] = p1, c.ss[i + 1] = b.sum + s1, c.sc[i + 1] = v1; break; }
        }
        ++c.s[0];
    }

    return c;
}
struct Segment_Tree {
    void Build(int o, int l, int r) {
        T[o].l = l, T[o].r = r;
        if (l == r) return T[o].ans = 1, T[o].sum = A[l], void();
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
        T[o] = T[o << 1] + T[o << 1 | 1];
    }
    void Change(int o, int l, int r, int x, int y) {
        if (l == r) return T[o].sum = 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);
        T[o] = T[o << 1] + T[o << 1 | 1];
    }
    node Query(int o, int l, int r, int L, int R) {
        if (L <= l && r <= R) return T[o];
        int mid = (l + r) >> 1;
        if (R <= mid) return Query(o << 1, l, mid, L, R);
        if (L > mid) return Query(o << 1 | 1, mid + 1, r, L, R);
        return Query(o << 1, l, mid, L, R) + Query(o << 1 | 1, mid + 1, r, L, R);
    }
} seg;
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> A[i];
    seg.Build(1, 1, n);
    cin >> q;
    while (q--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1) A[x] = y, seg.Change(1, 1, n, x, y);
        else cout << seg.Query(1, 1, n, x, y).ans << "\n";
    }
    return 0;
}

C 经典套路。

D,支配对。

E,拆式子。

G,点权最短路,每个点第一次被扩展即为答案,复杂度 \(\mathcal{O}(n \log n + m)\)\(\log\) 不在 \(m\) 而在 \(n\) 上。

J。线段树上,最基本的信息还是在叶子上。每次修改本质上要改的都是叶子,懒标记也是对叶子的懒影响,区间答案也只是在提前计算对区间所有叶子施加之后的新答案。如果能直接算就可以懒标记。

K,另一个做法是考虑排列的前缀最小值在 \(y = n - x\),而我们要求的答案就是前缀最小值折线和这条直线的交点个数。于是单侧递归即可。

L。直接线段树。只要能合并都行。

posted @ 2025-09-04 23:47  forgotmyhandle  阅读(9)  评论(0)    收藏  举报