Skip to content

查看源代码:
20251118.md

---
title: 一周训练记
createTime: 2025/11/18
categories:
    - IT
tags:
    - OI
---

## 注意策略、边界情况——[P14520 【MX-S11-T1】战争游戏](https://www.luogu.com.cn/problem/P14520)

策略确实考验注意力。需要想到一个退化情况——即有时优势方可以直接固守然后取得胜利。另外,由于忘了只有大于才能吃,我忘记看边界情况是否能取等,结果第一次提交 $\color{red}{\textbf{WA 56}}$ 了。

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn=1e5+100;
int a[maxn], s[maxn];
 
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int T; cin >> T;
    while (T--) {
        int n; cin >> n;
        for (int i=1; i<=n; i++) {
            cin >> a[i];
            s[i] = a[i] + s[i-1];
        }
        for (int i=1; i<=n-1; i++) {
            cout << (
                s[i] > s[n] - s[i]
                || a[i]>a[i+1] && a[i]+a[i+1]>=a[i+2] && s[i+1] > s[n] - s[i+1]
            );
        }
        cout << '\n';
    }    
    
    return 0;
}
```
:::

## 拆分问题、边界情况、`lower_bound`/`upper_bound`——[P14521 【MX-S11-T2】加减乘除](https://www.luogu.com.cn/problem/P14520)

拆分问题是很重要的。例如本题,你可以感受到每个结点 $u$ 存在一个区间 $[a_i, b_i]$,$u$ 可以被访问 $\iff x \in [a_i, b_i]$,然后问题转化为离线二维数点。

另外 `lower_bound`/`upper_bound` 的运用也需要注意。你可以手动代几个例子试一试。例如:

- 恰好有一个目标
- 有多个目标
- 没有目标
- 目标比开头小
- 目标比结尾大

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int maxn=5e5+10, maxq=1e6+10, inf=0x3f3f3f3f3f3f3f3f;
int n;
int a[maxn], p[maxn], l[maxn], r[maxn];
vector<int> G[maxn];
int ans[maxq];

struct query {
    int i, x, y;
    bool operator < (const query &b) const {
        return x < b.x;
    }
} qs[maxq];

struct point {
    int x, y;
    
    bool operator < (const point &b) const {
        return x < b.x;
    }
} points[maxn] = {
//  {0,0}, {1,-1}, {2, -3}, {2, -3}, {0, -1}, {-1, -3}
};

void dfs(int u, int add) {
    points[u] = {
        max(points[p[u]].x, l[u] - add),
        max(points[p[u]].y, -(r[u] - add))
    };
    
//  printf("#%d: add %d; x in [%d, %d]\n", u, add, points[u].x, -points[u].y);
    
    for (int v: G[u]) {
        dfs(v, add + a[u]);
    }
}

int ylsh[maxn];
int *lshend;
int ilsh(int x) {
    return upper_bound(ylsh+1, lshend, x) - ylsh - 1;
}

struct BIT {
    int t[maxn];
    
    inline int lowbit(int x) {
        return x & -x;
    }
    
    inline void add(int i, int a) {
        for (; i<=n; i+=lowbit(i)) {
            t[i] += a;
        }
    }
    
    inline int query(int i) {
        int ans = 0;
        for (; i; i-=lowbit(i)) {
            ans += t[i];
        }
        return ans;
    }
} bit;

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
//  freopen("calc2.in", "r", stdin);
//  freopen("calc.out", "w", stdout);
    
    int q; cin >> n >> q;
    
    l[1] = -inf; r[1] = inf;
    for (int i=2; i<=n; i++) {
        cin >> p[i] >> l[i] >> r[i];
        G[p[i]].push_back(i);
    }
    
    for (int i=1; i<=n; i++) {
        char op;
        cin >> op >> a[i];
        if (op == '-') { a[i] = -a[i]; }
    }
    
    for (int i=1; i<=q; i++) {
        cin >> qs[i].x;
        qs[i].i = i;
        qs[i].y = -qs[i].x;
    }
    
    points[0] = {-inf, -inf};
    dfs(1, 0);
    
    sort(points+1, points+n+1);
    for (int i=1; i<=n; i++) {
        ylsh[i] = points[i].y;
    }
    sort(ylsh+1, ylsh+n+1);
    lshend = unique(ylsh+1, ylsh+n+1);
    sort(qs+1, qs+q+1);
    
    int nxp = 1;
    for (int i=1; i<=q; i++) {
        while (nxp <= n && points[nxp].x <= qs[i].x) {
//          printf("add x=%d && y=%d\n", points[nxp].x, points[nxp].y);
            bit.add(ilsh(points[nxp].y), 1);
            nxp++;
        }
        
//      printf("proc x=%d\n", qs[i].x);
        
        ans[qs[i].i] = bit.query(ilsh(qs[i].y));
    }
    
    for (int i=1; i<=q; i++) {
        cout << ans[i] << '\n';
    }
    
    return 0;
}
```
:::

## 拆分、简化问题——[P14378 【MX-S9-T1】「LAOI-16」签到](https://www.luogu.com.cn/problem/P14378)

首先拆分问题,看看不带修怎么做。显然将 $a_i$ 正序排列,加的数倒序排列即可。

然后看看带修怎么做。每次只有一处修改,则在排序后相当于局部轮换。接下来分 $3$ 种情况:没有改动的、被轮换的、被修改的点本身。显然轮换之后的值可以预处理(毕竟只有左移、右移两种),那么转换成一个 RMQ 问题。

我因为想练线段树所以写了线段树,实际上可以用 ST 表。

题解的分段过于复杂,这凸显出简化问题的重要性(事实上但凡是大分段写着都很烦)。

另外复制代码记得改好,不要复制完 `max` 忘记改成 `min` 了。

:::details Code
```cpp
// 糖:忘记排序/修改……
// 糖:分类忘分一段
// 糖:右移无视空位导致下标额外 +1 

#include <bits/stdc++.h>
using namespace std;

inline int max4(int a, int b, int c, int d) {
    return max(max(a, b), max(c, d));
}

inline int min4(int a, int b, int c, int d) {
    return min(min(a, b), min(c, d));
}

const int maxn = 5e5+10, inf = 0x3f3f3f3f;

int n, k, q;
int a[maxn], asort[maxn];

struct color {
    int b, c;
    bool operator < (const color &x) const {
        return c > x.c;
    }
} co[maxn];

int add[maxn];
int v[maxn], lsv[maxn], rsv[maxn];

struct mxst {
    int t[4*maxn];
    
    inline int md(int x, int y) { return (x+y)>>1; }
    inline int lc(int x) { return x<<1; }
    inline int rc(int x) { return (x<<1)|1; }
    
    void build(int tn, int tl, int tr, int *a){
        if (tl == tr) {
            t[tn] = a[tl];
            return;
        }
        
        int mid = md(tl, tr);
        build(lc(tn), tl, mid, a);
        build(rc(tn), mid+1, tr, a);
        t[tn] = max(t[lc(tn)], t[rc(tn)]);
    }
    
    int query(int tn, int tl, int tr, int x, int y) {
        if (tr < x || y < tl) {
            return -inf;
        }
        
        if (x <= tl && tr <= y) {
            return t[tn];
        }
        
        int mid = md(tl, tr);
        return max(
            query(lc(tn), tl, mid, x, y),
            query(rc(tn), mid+1, tr, x, y)
        );
    }
} mxv, lsmxv, rsmxv;

struct mnst {
    int t[4*maxn];
    
    inline int md(int x, int y) { return (x+y)>>1; }
    inline int lc(int x) { return x<<1; }
    inline int rc(int x) { return (x<<1)|1; }
    
    void build(int tn, int tl, int tr, int *a){
        if (tl == tr) {
            t[tn] = a[tl];
            return;
        }
        
        int mid = md(tl, tr);
        build(lc(tn), tl, mid, a);
        build(rc(tn), mid+1, tr, a);
        t[tn] = min(t[lc(tn)], t[rc(tn)]);
    }
    
    int query(int tn, int tl, int tr, int x, int y) {
        if (tr < x || y < tl) {
            return inf;
        }
        
        if (x <= tl && tr <= y) {
            return t[tn];
        }
        
        int mid = md(tl, tr);
        return min(
            query(lc(tn), tl, mid, x, y),
            query(rc(tn), mid+1, tr, x, y)
        );
    }
} mnv, lsmnv, rsmnv;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    cin >> n >> k >> q;
    for (int i=1; i<=n; i++) {
        cin >> a[i];
        asort[i] = a[i];
    }
    sort(asort+1, asort+n+1);
    
    for (int i=1; i<=k; i++) {
        cin >> co[i].b;
    }
    for (int i=1; i<=k; i++) {
        cin >> co[i].c;
    }
    sort(co+1, co+k+1);
    
    int tot=0;
    for (int i=1; i<=k; i++) {
        while (co[i].b--) {
            tot++;
            add[tot] = co[i].c;
//          printf("+%d\n", add[i]);
        }
    }
    
    for (int i=1; i<=n; i++) {
        v[i] = asort[i] + add[i];
        lsv[i] = asort[i] + add[i-1];
        rsv[i] = asort[i] + add[i+1];
//      printf(":%d,%d,%d\n", v[i], lsv[i], rsv[i]);
    }
    
    mxv.build(1, 1, n, v);
    lsmxv.build(1, 1, n, lsv);
    rsmxv.build(1, 1, n, rsv);
    mnv.build(1, 1, n, v);
    lsmnv.build(1, 1, n, lsv);
    rsmnv.build(1, 1, n, rsv);
    
//  printf("now: %d - %d\n", mxv.query(1, 1, n, 1, n), mnv.query(1, 1, n, 1, n));
    
    while (q--) {
        int x, v; cin >> x >> v;
        int src = lower_bound(asort+1, asort+n+1, a[x]) - asort;
            
//      printf("sorted: %d->%d\n", src, dst);
        if (a[x] == v) {
            cout << mxv.query(1, 1, n, 1, n) - mnv.query(1, 1, n, 1, n) << '\n';
        } else if (a[x] > v) {
            int dst = lower_bound(asort+1, asort+n+1, v) - asort;
            int mx = max4(
                mxv.query(1, 1, n, 1, dst-1),
                rsmxv.query(1, 1, n, dst, src-1),
                v + add[dst],
                mxv.query(1, 1, n, src+1, n)
            ), mn = min4(
                mnv.query(1, 1, n, 1, dst-1),
                rsmnv.query(1, 1, n, dst, src-1),
                v + add[dst],
                mnv.query(1, 1, n, src+1, n)
            );
            cout << mx - mn << '\n';
//          printf("ans = %d - %d\n", mx, mn);
        } else {
            int dst = lower_bound(asort+1, asort+n+1, v) - asort - 1;
            int mx = max4(
                mxv.query(1, 1, n, 1, src-1),
                v + add[dst],
                lsmxv.query(1, 1, n, src+1, dst),
                mxv.query(1, 1, n, dst+1, n)
            ), mn = min4(
                mnv.query(1, 1, n, 1, src-1),
                v + add[dst],
                lsmnv.query(1, 1, n, src+1, dst),
                mnv.query(1, 1, n, dst+1, n)
            );
            cout << mx - mn << '\n';
//          printf("ans = %d - %d\n", mx, mn);
        }
    }
    
    return 0;
}
```
:::

## 贪心、`long long`——[P3545 [POI 2012] HUR-Warehouse Store](https://www.luogu.com.cn/problem/P3545)

:::details Code
```cpp
// 不开 long long 见祖宗 

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int maxn=3e5;
int a[maxn], b[maxn];

typedef pair<int, int> pii;
#define _1 first
#define _2 second

priority_queue<pii, vector<pii>, less<pii>> pq;

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int n; cin >> n;
    for (int i=1; i<=n; i++) {
        cin >> a[i];
    }
    for (int i=1; i<=n; i++) {
        cin >> b[i];
    }
    
    int goods = 0, ans = 0;
    for (int i=1; i<=n; i++) {
        goods += a[i];
        if (goods > b[i]) {
            goods -= b[i];
            ans++;
            pq.push({b[i], i});
        } else if (!pq.empty() && pq.top()._1 > b[i]) {
            goods += pq.top()._1 - b[i];
            pq.pop();
            pq.push({b[i], i});
        }
    }
    
    vector<int> ansv;
    while (!pq.empty()) {
        ansv.push_back(pq.top()._2);
        pq.pop();
    }
    
    sort(ansv.begin(), ansv.end());
    cout << ans << '\n';
    for (int i: ansv) {
        cout << i << ' ';
    }
    
    return 0;
}
```
:::

## 贪心、数学调整法、辨明相似变量——[P6927 [ICPC 2016 WF] Swap Space](https://www.luogu.com.cn/problem/P6927)

之前对于格式化减容量的硬盘思路错了。实际上,使用调整法(或者说邻项交换法)可以做。

假设现在剩余空间为 $s$,则先 $1$ 后 $2$,两次升级的代价分别为

$$
\begin{align*}
&\phantom{=} \Bigl( \max\{a_1 - s, 0\} \Bigr) + \Bigl( \max\{a_2 - (\max\{a_1-s, 0\} + s - (a_1-b_1)), 0 \} \Bigr) \\
&= \max\bigl\{a_2 + a_1-b_1, a_1 - s, 0 \bigr\} \\
&= a_1 + a_2 - b_1
\end{align*}
$$

同理先 $2$ 后 $1$ 的代价为 $a_1 + a_2 - b_2$,于是按照 $b_i$ 降序排列。

目前因为一个测试点不知原因输出 $0$ 所以 $\color{red}{\textbf{WA}}$。

:::details Code
```cpp
// 坑:贪心推错

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int maxn=1e6+10;

struct disk {
    int a, b;
} d1[maxn], d2[maxn];
int cnt1, cnt2;

inline bool cmp1(const disk &x, const disk &y) {
    return x.a < y.a;
}

inline bool cmp2(const disk &x, const disk &y) {
    return x.b > y.b;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int n; cin >> n;
    
    for (int i=1; i<=n; i++) {
        int a, b; cin >> a >> b;
        if (a <= b) {
            cnt1++;
            d1[cnt1] = {a, b};
        } else {
            cnt2++;
            d2[cnt2] = {a, b};
        }
    }
    
    sort(d1+1, d1+cnt1+1, cmp1);
    sort(d2+1, d2+cnt2+1, cmp2);
    
    int space=0, ans=0;
    for (int i=1; i<=cnt1; i++) {
        if (space < d1[i].a) {
            ans += d1[i].a - space;
            space = d1[i].a;
        }
        space += d1[i].b - d1[i].a;
    }
    
    for (int i=1; i<=cnt1; i++) {
        if (space < d2[i].a) {
            ans += d2[i].a - space;
            space = d2[i].a;
        }
        if (space < d2[i].b) {
            ans += d2[i].b - space;
            space = d2[i].b;
        }
        space -= d2[i].b;
    }
    
    cout << ans;
    
    return 0;
}
```
:::

## 贪心、简化问题、`fc`——[P8113 [Cnoi2021] 自我主义的平衡者](https://www.luogu.com.cn/problem/P8113)

:::details Code
```cpp
// 非经典田忌赛马
// 糖:多打了个空格,全 WA 了 

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 1e5+100;
int a[maxn];

signed main() {
    int n, m; cin >> n >> m;
    for (int i=1; i<=n; i++) {
        cin >> a[i];
    }
    
    sort(a+1, a+n+1);
    int cnt1 = 0;
    for (int i=1; i<=n; i++) {
        if (a[i]*(i-1) >= cnt1*m) {
            cnt1++;
        }
    }
    
    sort(a+1, a+n+1, greater<int>());
    int cnt2 = 0;
    for (int i=1; i<=n; i++) {
        if (a[i]*(i-1) >= cnt2*m) {
            cnt2++;
        }
    }
    
    printf("%.2f %.2f", cnt1*m*1./n, cnt2*m*1./n);
    
    return 0;
}
```
:::

## 发现性质、简化做法——[P14379 【MX-S9-T2】「LAOI-16」摩天大楼](https://www.luogu.com.cn/problem/P14379?contestId=287506)

试了两种不同的做法。但是写挂了 QwQ。

## 拆分问题、化归——[P5521 [yLOI2019] 梅深不见冬](https://www.luogu.com.cn/problem/P5521)

考虑 DP,看看一个结点怎么转移。激活子结点的过程可以看作“付出代价(需要梅花)+获得回馈(余下梅花)”,设激活结点 $i$ 需要 $f(i)$,则回馈为 $g(i) = f(i) - w_i$。

由熟知结论
- $f(i) \le g(i)$ 在前,按 $f(i)$ 升序 {style="color:gray;"}
- **$f(i) > g(i)$ 在后,按 $g(i)$ 降序**

然后依题意模拟 + DP 即可,注意还需要激活当前结点、回收子结点。

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+100;
vector<int> G[maxn];
int p[maxn], w[maxn];

int f[maxn]/*放梅花*/, c[maxn]/*留多余下梅花*/, ans[maxn]; 

bool cmp(int x, int y) {
    if (c[x]>f[x] && c[y]<f[y]) {
        return true;
    } else if (c[x]<f[x] && c[y]>f[y]) {
        return false;
    } else if (c[x]>f[x]) {
        return f[x] < f[y];
    } else {
        return c[x] > c[y];
    }
}

void dp(int u) {
    for (int v: G[u]) {
        dp(v);
    }
    
    sort(G[u].begin(), G[u].end(), cmp);
    
    for (int v: G[u]) {
        if (f[v] > c[u]) {
            f[u] += f[v] - c[u];
            c[u] = c[v];
        } else {
            c[u] += c[v] - f[v];
        }
    }
    
    if (w[u] > c[u]) {
        f[u] += w[u] - c[u];
        c[u] = 0;
    } else {
        c[u] -= w[u];
    }
    
    for (int v: G[u]) {
        c[u] += w[v];
    }
    
//  printf("f(%d)=%d, c(%d)=%d\n", u, f[u], u, c[u]);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int n; cin >> n;
    for (int i=2; i<=n; i++) {
        cin >> p[i];
        G[p[i]].push_back(i);
    }
    for (int i=1; i<=n; i++) {
        cin >> w[i];
    }
    
    dp(1);
    
    for (int i=1; i<=n; i++) {
        cout << f[i] << ' ';
    }
    
    return 0;
}
```
:::

## 双倍经验——[P4053 [JSOI2007] 建筑抢修](https://www.luogu.com.cn/problem/P4053)

时间就是金钱。把时间看成货物,实际上就是一个典中典反悔贪心。

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=15e4+10;

struct building {
    int t1, t2;
    
    bool operator < (const building &b) const {
        return t2 < b.t2;
    }
} bd[maxn];

priority_queue<int, vector<int>, less<int>> pq;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int n; cin >> n;
    for (int i=1; i<=n; i++) {
        cin >> bd[i].t1 >> bd[i].t2;
    }
    
    sort(bd+1, bd+n+1);
    
    int free=0, ans=0;
    for (int i=1; i<=n; i++) {
        free += bd[i].t2 - bd[i-1].t2;
        if (free >= bd[i].t1) {
            free -= bd[i].t1;
            pq.push(bd[i].t1);
            ans++;
        } else if (!pq.empty() && bd[i].t1 < pq.top()) {
            free += pq.top() - bd[i].t1;
            pq.pop();
            pq.push(bd[i].t1); 
        }
    }
    
    cout << ans;
    
    return 0;
}
```
:::

## 先合法再最优——[P6411 [COCI 2008/2009 #3] MATRICA](https://www.luogu.com.cn/problem/P6411)

:::details Code (90pts)
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=3e4+10, maxk=30;

int n;
int col[maxn];

struct rule {
    char c;
    int a;
    
    inline bool operator < (const rule &b) const {
        return c < b.c;
    }
} rules[maxk];

struct block {
    int l, r;
    char c;
    
    inline bool operator < (const block &b) const {
        return l < b.l;
    }
};

char diag[maxn];
vector<block> blk[maxn];
int cur_diag=1, x=1, y=2;

void add_diag(char c) {
    diag[cur_diag] = c;
    cur_diag++;
}

int add(int a, char c) {
    int len = min(a/2, n-y+1);
    blk[x].push_back({y, y+len-1, c});
    
    y += len;
    if (y > n) {
        x++;
        y = x+1;
    }
    
    return len * 2;
}

char get(int x, int y) {
    if (x == y) {
        return diag[x];
    }
    
    if (x > y) {
        swap(x, y); 
    }
    
    return (upper_bound(
        blk[x].begin(),
        blk[x].end(),
        block{y, 0, '#'}
    ) - 1) -> c;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int k; cin >> n >> k;
    
    int odd=0;
    for (int i=1; i<=k; i++) {
        cin >> rules[i].c >> rules[i].a;
        
        odd += rules[i].a & 1;
        if (odd > n) {
            cout << "IMPOSSIBLE";
            return 0;
        }
    }
    sort(rules+1, rules+k+1);
    
    for (int i=1; i<=k; i++) {
        char c = rules[i].c;
        int a = rules[i].a;
        
        while (a) {
            if (x >= cur_diag) {
                add_diag(c);
                a--;
            }
            if (a % 2) {
                add_diag(c);
                a--;
            }
            
            a -= add(a, c);
        }
    }
    
//  for (int i=1; i<=n; i++) {
//      for (block &b: blk[i]) {
//          printf("(%d,%d,%c) ", b.l, b.r, b.c);
//      }
//      printf("\n");
//  }

    int p; cin >> p;
    for (int i=1; i<=p; i++) {
        cin >> col[i];
    }
    for (int x=1; x<=n; x++) {
        for (int i=1; i<=p; i++) {
            cout << get(x, col[i]);
        }
        cout << '\n';
    }
    
    return 0;
}
```
:::

## 拆分问题——[P6412 [COCI 2008/2009 #3] BST](https://www.luogu.com.cn/problem/P6412)

拆分成:

1. 找到插入点
2. 获取深度

维护插入点区间,用类似珂朵莉树的手法。

:::details
```cpp
#include <bits/stdc++.h>
using namespace std;

const int inf=0x3f3f3f3f;

struct inspt {
    int l, r;
    int dep;
    
    bool operator < (const inspt &b) const {
        return l < b.l;
    }
};

set<inspt> bst;

long long c;

void insert(int x) {
    auto it = bst.upper_bound({x, 0, 0});
    it--;
    
    int l=it->l, r=it->r, dep=it->dep;
    
    c += dep;
    bst.erase(it);
    
    if (l <= x-1) {
        bst.insert({l, x-1, dep+1});
    }
    if (x+1 <= r) {
        bst.insert({x+1, r, dep+1});
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int n; cin >> n;
    bst.insert({-inf, inf, 0});
    
    for (int i=1; i<=n; i++) {
        int a; cin >> a;
        insert(a);
        cout << c << '\n';
    }
    
    return 0;
}
```
:::

## 数据范围提示做法——[P4302 [SCOI2003] 字符串折叠](https://www.luogu.com.cn/problem/P4302)

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=128;
char s[maxn];
int f[maxn][maxn];

int declen(int x) {
    int ans=0, p10=1;
    while (x > p10-1) {
        p10 *= 10;
        ans++;
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    cin >> (s+1);
    int n = strlen(s+1);
    
    memset(f, 0x3f, sizeof(f));
    for (int i=1; i<=n; i++) {
        f[i][i] = 1;
    }
    
    for (int len=2; len<=n; len++) {
        for (int l=1; l+len-1<=n; l++) {
            int r = l+len-1;
            
            // 不折叠
            for (int mid=l; mid<r; mid++) {
                f[l][r] = min(f[l][r], f[l][mid]+f[mid+1][r]);
            }
            
            // 折叠
            for (int fold=1; fold<=len/2; fold++) {
                if (len % fold) {
                    continue;
                }
                
                for (int i=l+fold; i<=r; i++) {
                    if (s[i] != s[i-fold]) {
                        goto nxt;
                    }
                }
                
//              printf("[%d, %d] can fold %d\n", l, r, fold);
                f[l][r] = min(f[l][r], declen(len/fold)+2+f[l][l+fold-1]);
            
                nxt:;
            }
            
//          printf("f(%d, %d) = %d\n", l, r, f[l][r]);
        }
    }
    
    cout << f[1][n];
    
    return 0;
}
```
:::

## [P3389 【模板】高斯消元法](https://www.luogu.com.cn/problem/P3389)

数学拓展班恰好讲了,遂做。

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=128;
int n;
double a[maxn][maxn], b[maxn];
double x[maxn];

inline void subx(int i, int j, double k) {
    for (int c=1; c<=n; c++) {
        a[i][c] -= a[j][c] * k;
    }
    b[i] -= b[j] * k;
}

inline void swp(int i, int j) {
    for (int c=1; c<=n; c++) {
        swap(a[i][c], a[j][c]);
    }
    swap(b[i], b[j]);
}

bool gauss() {
    for (int i=1; i<=n; i++) {
        int row=-1;
        for (int r=i; r<=n; r++) {
            if (abs(a[r][i]) > 1e-6) {
                row = r;
                break;
            }
        }

        if (row == -1) {
            return false;
        }

        if (i != row) {
            swp(i, row);
        }

        for (int j=i+1; j<=n; j++) {
            subx(j, i, a[j][i]/a[i][i]);
        }
    }

    for (int i=n; i>=1; i--) {
        double t=b[i];
        for (int j=i+1; j<=n; j++) {
            t -= a[i][j] * x[j];
        }
        x[i] = t / a[i][i];
    }

    return true;
}

int main() {
    cin >> n;
    for (int i=1; i<=n; i++) {
        for (int j=1; j<=n; j++) {
            cin >> a[i][j];
        }
        cin >> b[i];
    }

    if (!gauss()) {
        cout << "No Solution";
        return 0;
    }

    for (int i=1; i<=n; i++) {
        printf("%.2f\n", x[i]);
    }

    return 0;
}
```
:::

## 树形 DP 套贪心、调整、变量大小关系、交叉主项公式、树形父子依赖——[P3574 [POI 2014] FAR-FarmCraft](https://www.luogu.com.cn/problem/P3574)

树形算法看清楚父子之间的依赖,别搞错了。

另外,根据变量大小关系,我们有交叉主项公式简化贪心调整:

:::lemma 交叉主项公式
若 $a \ge c, b \le d$,则
$$\operatorname{cmp}(\max\{a, b\}, \max\{c, d\}) = \operatorname{cmp}(a, d)$$
其中 $\operatorname{cmp}(a, b) = \operatorname{sgn}(a-b) = \begin{cases}1,&a>b\\0,&a=b\\-1,&a<b\end{cases}$
:::

:::details
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=5e5+10;
int n;
int c[maxn];
vector<int> G[maxn];
int siz[maxn];

int f[maxn];  // f: 从刚刚进入到全部安装完成 

bool cmp(int x, int y) {
    return 2*siz[x] - f[x] < 2*siz[y] - f[y];
}

void dp(int u, int p) {
    siz[u] = 1;
    for (int v: G[u]) {
        if (v != p) {
            dp(v, u);
            siz[u] += siz[v];
        }
    }
    
    sort(G[u].begin(), G[u].end(), cmp);
    int drive=0;
    for (int v: G[u]) {
        if (v == p) {
            continue;
        }
        
        f[u] = max(f[u], drive+1+f[v]);
        drive += 2 * siz[v];
    }
    
    if (u == 1) {
        f[u] = max(f[u], 2*n-2+c[u]);
    } else {
        f[u] = max(f[u], c[u]);
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    cin >> n;
    for (int i=1; i<=n; i++) {
        cin >> c[i]; 
    }
    for (int i=1; i<=n-1; i++) {
        int u, v; cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u); 
    }
    
    dp(1, -1);
    
    cout << f[1];
    
    return 0;
}
```
:::

## 树上背包——[P1272 重建道路](https://www.luogu.com.cn/problem/P1272)

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=160;
int n, p;
vector<int> G[maxn];
int f[maxn][maxn];

void dp(int u, int pa) {
    f[u][1] = 0;
    
    for (int v: G[u]) {
        if (v == pa) {
            continue;
        }
        
        dp(v, u);
        
        for (int i=n; i>=1; i--) {
            f[u][i]++;
            for (int j=1; j<=i-1; j++) {
                f[u][i] = min(f[u][i], f[u][i-j]+f[v][j]);
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    memset(f, 0x3f, sizeof(f));
    
    cin >> n >> p;
    for (int i=1; i<=n-1; i++) {
        int u, v; cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    
    dp(1, -1);
    
    int ans = f[1][p];
    for (int i=1; i<=n; i++) {
        ans = min(ans, f[i][p]+1);
    }
    cout << ans;
    
    return 0;
}
```
:::

## `long long`——[P4158 [SCOI2009] 粉刷匠](https://www.luogu.com.cn/problem/P4158)

:::details
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=60, maxt=3000;
int n, m, t;
int f[maxn][maxn][maxt];
char s[maxn][maxn];
int sum[maxn][maxn];

inline int paint(int row, int l, int r) {
    int ones = sum[row][r] - sum[row][l-1];
    return max(ones, r-l+1-ones);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    cin >> n >> m >> t;
    for (int i=1; i<=n; i++) {
        cin >> (s[i]+1);
    }
    
    for (int i=1; i<=n; i++) {
        for (int j=1; j<=m; j++) {
            sum[i][j] = sum[i][j-1] + (s[i][j]=='1');
        }
    }
    
    // O(n^3 T)
    for (int i=1; i<=n; i++) {
        for (int k=0; k<=t; k++) {
            f[i][0][k] = f[i-1][m][k];
        }
        
        for (int j=1; j<=m; j++) {
            for (int k=0; k<=t; k++) {
                f[i][j][k] = f[i][j-1][k];
            }
            
            for (int s=1; s<=j; s++) {
                for (int k=1; k<=t; k++) {
                    f[i][j][k] = max(f[i][j][k], 
                        f[i][s-1][k-1] + paint(i, s, j)
                    );
                }
            }
        }
    }
    
    cout << f[n][m][t];
    
    return 0;
}
```
:::

## 拆分问题、相似变量混淆、线段树模板、分段调试——[P2894 [USACO08FEB] Hotel G](https://www.luogu.com.cn/problem/P2894)

维护最长连续空闲段。另注意到第一个连续空闲 $x$ 房间的结尾 $=$ 第一个使得 $[1,i]$ 有 $x$ 个房间的 $i$。打个倍增就好了。(我偷懒打了 $O(n \log^2 n)$ 的,也很快)

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=5e4+100, maxlogn=16;
int n, m;

struct node {
    int siz, l, r;
    int len;
    bool clr;
};

inline node merge (const node &a, const node &b) {
    if (a.clr && b.clr) {
        return {0, 0, 0, a.len+b.len, true};
    } else if (a.clr) {
        return {
            b.siz, a.len+b.l, b.r,
            a.len+b.len, false
        };
    } else if (b.clr) {
        return {
            a.siz, a.l, a.r+b.len,
            a.len+b.len, false
        };
    } else {
        return {
            max(max(a.siz, b.siz), a.r+b.l),
            a.l, b.r, a.len+b.len, false
        };
    }
}

inline void fillnode(node &a) {
    a.siz = a.l = a.r = a.clr = 0;
}

inline int value(const node &a) {
    return a.clr? a.len: max(max(a.l, a.r), a.siz);
}

char NORM=0, CLR=1, FILL=2;

struct segtree {
    node tree[maxn*4];
    char lz[maxn*4];
    
    inline int lc(int x) { return x<<1; }
    inline int rc(int x) { return (x<<1)|1; }
    inline int md(int x, int y) { return (x+y)>>1; }
    
    void build(int tn, int tl, int tr) {
        tree[tn].len = tr - tl + 1;
        if (tl != tr) {
            int mid = md(tl, tr);
            build(lc(tn), tl, mid);
            build(rc(tn), mid+1, tr);
        }
    }
    
    void pushup(int tn) {
        tree[tn] = merge(tree[lc(tn)], tree[rc(tn)]);
        
//      if (clr[lcn] && clr[rcn]) {
//          clr[tn] = true;
//      } else if (clr[lcn]) {
//          clr[tn] = false;
//          siz[tn] = siz[rcn];
//          l[tn] = mid-tl+1 + l[rcn];
//          r[tn] = r[rcn];
//      } else if (clr[rcn]) {
//          clr[tn] = false;
//          siz[tn] = siz[lcn];
//          r[tn] = r[lcn] + tr-mid;
//          l[tn] = l[lcn];
//      } else {
//          clr[tn] = false;
//          siz[tn] = max(siz[lcn], siz[rcn], r[lcn]+l[rcn]);
//          l[tn] = l[lcn];
//          r[tn] = r[rcn];
//      }
    }
    
    void pushdown(int tn) {
        int lcn=lc(tn), rcn=rc(tn);
        if (lz[tn] == CLR) {
            lz[tn] = NORM;
            tree[lcn].clr = tree[rcn].clr = true;
            lz[lcn] = lz[rcn] = CLR;
        } else if (lz[tn] == FILL) {
            lz[tn] = NORM;
            fillnode(tree[lcn]);
            fillnode(tree[rcn]);
            lz[lcn] = lz[rcn] = FILL;
        }
    }
    
    node query(int tn, int tl, int tr, int x, int y) {
        if (tr<x || y<tl) {
            return {0, 0, 0, 0, 0};
        }
        
        if (x<=tl && tr<=y) {
//          printf("[%d,%d]: [%d] %d-%d-%d\n", tl, tr, tree[tn].clr, tree[tn].l, tree[tn].siz, tree[tn].r);
            return tree[tn];
        }
        
//      printf("to #%d[%d, %d]\n", tn, tl, tr);
        
        pushdown(tn);
        
        int mid = md(tl, tr);
        
//      node no = merge(
//          query(lc(tn), tl, mid, x, y),
//          query(rc(tn), mid+1, tr, x, y)
//      );
//      printf("[%d,%d]: [%d] %d-%d-%d\n", tl, tr, no.clr, no.l, no.siz, no.r);
//      return no;
        
        return merge(
            query(lc(tn), tl, mid, x, y),
            query(rc(tn), mid+1, tr, x, y)
        );
    }
    
    void clear(int tn, int tl, int tr, int x, int y) {
        if (tr<x || y<tl) {
            return;
        }
        
        if (x<=tl && tr<=y) {
            tree[tn].clr = true;
            lz[tn] = CLR;
            return;
        }
        
        pushdown(tn);
        
        int mid = md(tl, tr);
        clear(lc(tn), tl, mid, x, y);
        clear(rc(tn), mid+1, tr, x, y);
        pushup(tn);
        
//      printf("[%d,%d]: [%d] %d-%d-%d\n", tl, tr, tree[tn].clr, tree[tn].l, tree[tn].siz, tree[tn].r);
    }
    
    void fill(int tn, int tl, int tr, int x, int y) {
        if (tr<x || y<tl) {
            return;
        }
        
        if (x<=tl && tr<=y) {
            fillnode(tree[tn]);
            lz[tn] = FILL;
            return;
        }
        
        pushdown(tn);
        
        int mid = md(tl, tr);
        fill(lc(tn), tl, mid, x, y);
        fill(rc(tn), mid+1, tr, x, y);
        pushup(tn);
        
//      printf("[%d,%d]: [%d] %d-%d-%d\n", tl, tr, tree[tn].clr, tree[tn].l, tree[tn].siz, tree[tn].r);
    }
} sgt;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    cin >> n >> m;
    
    sgt.build(1, 1, n);
    sgt.tree[1].clr = true;
    sgt.lz[1] = CLR;
    
//  while (m--) {
//      char c; int x, y;
//      cin >> c >> x >> y;
//      if (c == 'f') {
//          sgt.fill(1, 1, n, x, y);
//      } else if (c == 'c') {
//          sgt.clear(1, 1, n, x, y);
//      } else if (c == 'q') {
//          node no = sgt.query(1, 1, n, x, y);
//          printf("%d\n", no.clr? no.len: max(max(no.l, no.r), no.siz));
//      }
//  }

    while (m--) {
        int i; cin >> i;
        if (i == 1) {
            int x; cin >> x;
            int r=0;
            for (int j=maxlogn; j>=0; j--) {
                if (r + (1<<j) > n) {
                    continue;
                }
                if (value(sgt.query(1, 1, n, 1, r+(1<<j))) < x) {
                    r += 1<<j;
                }
            }
            
            if (r == n) {
                cout << "0\n";
            } else {
                cout << r-x+2 << '\n';
                sgt.fill(1, 1, n, r-x+2, r+1);
            }
        } else {
            int x, y; cin >> x >> y;
            sgt.clear(1, 1, n, x, x+y-1);
        }
    }
    
    return 0;
}
```
:::

## 树链剖分、线段树、四倍数组、`long long`——[P3178 [HAOI2015] 树上操作](https://www.luogu.com.cn/problem/P3178)

仍然建议数据结构不带壳先测一遍。

板子记熟。

另外要注意与数据范围有关的细枝末节。

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
typedef long long ll;

int n, m;
vector<int> G[maxn];
int aa[maxn];

int p[maxn], siz[maxn], wson[maxn];
void dfs1(int u, int fa) {
    p[u] = fa;
    siz[u] = 1;
    int wsiz = 0;
    
    for (int v: G[u]) {
        if (v == fa) {
            continue;
        }
        
        dfs1(v, u);
        siz[u] += siz[v];
        
        if (siz[v] > wsiz) {
            wsiz = siz[v];
            wson[u] = v;
        }
    }
}

int dfn[maxn], idfn[maxn], top[maxn];
int dft;

void dfs2(int u, int tp) {
    dft++;
    dfn[u] = dft;
    idfn[dft] = u;
    top[u] = tp;
    
//  printf("%d: dfn=%d, top=%d\n", u, dft, tp);
    
    if (wson[u]) {
        dfs2(wson[u], tp);
    }
    
    for (int v: G[u]) {
        if (v == wson[u] || v == p[u]) {
            continue;
        }
        
        dfs2(v, v);
    }
}

struct segtree {
    ll a[maxn*4], lz[maxn*4];
    
    inline int md(int x, int y) { return (x+y)>>1; }
    inline int lc(int x) { return x<<1; }
    inline int rc(int x) { return (x<<1)|1; }
    
    inline void pushup(int tn) {
        a[tn] = a[lc(tn)] + a[rc(tn)];
    }
    
    inline void pushdown(int tn, int tl, int tr) {
        int lcn=lc(tn), rcn=rc(tn), mid=md(tl,tr);
        a[lcn] += lz[tn]*(mid-tl+1); a[rcn] += lz[tn]*(tr-mid);
        lz[lcn] += lz[tn]; lz[rcn] += lz[tn];
        lz[tn] = 0;
    }
    
    void build(int tn, int tl, int tr) {
        if (tl == tr) {
            a[tn] = aa[idfn[tl]];
            return;
        }
        
        int mid = md(tl, tr);
        build(lc(tn), tl, mid);
        build(rc(tn), mid+1, tr);
        pushup(tn);
//      printf("#%d[%d, %d] -> %d\n", tn, tl, tr, a[tn]);
    }
    
    void add(int tn, int tl, int tr, int x, int y, ll w) {
        if (tr<x || y<tl) {
            return;
        }
        
        if (x<=tl && tr<=y) {
            a[tn] += w * (tr-tl+1);
            lz[tn] += w;
            return;
        }
        
        pushdown(tn, tl, tr);
        
        int mid = md(tl, tr);
        add(lc(tn), tl, mid, x, y, w);
        add(rc(tn), mid+1, tr, x, y, w);
        pushup(tn);
    }
    
    ll query(int tn, int tl, int tr, int x, int y) {
        if (tr<x || y<tl) {
//          printf("query #%d[%d, %d] -> %d (empty)\n", tn, tl, tr, 0);
            return 0;
        }
        
        if (x<=tl && tr<=y) {
//          printf("query #%d[%d, %d] -> %d (contain)\n", tn, tl, tr, a[tn]);
            return a[tn];
        }
        
        pushdown(tn, tl, tr);
        
        int mid = md(tl, tr);
        ll ret = (query(lc(tn), tl, mid, x, y)
                + query(rc(tn), mid+1, tr, x, y));
//      printf("query #%d[%d, %d] -> %d\n", tn, tl, tr, ret);
        return ret;
    }
} sgt;

ll query(int u) {
    ll ans=0;
    while (u) {  // p[1] = 0
        ans += sgt.query(1, 1, n, dfn[top[u]], dfn[u]);
        u = p[top[u]];
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    cin >> n >> m;
    for (int i=1; i<=n; i++) {
        cin >> aa[i];
    }
    for (int i=1; i<=n-1; i++) {
        int u, v; cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    
    dfs1(1, 0);
    dfs2(1, 1);
    sgt.build(1, 1, n);
    
    while (m--) {
        int op; cin >> op;
        if (op == 1) {
            int x, a; cin >> x >> a;
            sgt.add(1, 1, n, dfn[x], dfn[x], a);
        } else if (op == 2) {
            int x, a; cin >> x >> a;
            sgt.add(1, 1, n, dfn[x], dfn[x]+siz[x]-1, a);
        } else if (op == 3) {
            int x; cin >> x;
            cout << query(x) << '\n';
        }
    }
    
    return 0;
}
```
:::