4.3 淘天 
T1 询问区间  的元素拼接起来是不是  的倍数,正解是计算拆位前缀和,但是我直接用数的前缀和居然也过了,为什么呢?因为拼接相当于 x*10..0+y,而 x%3 和 x*10..0%3 是等价的,因为 x*10..0=99.9x+x,而前者模  是不算的,所以得证,假如拼接起来可以,数字和也可以,反之亦然
T2 问区间染色的最少次数,条件是单个元素或者  满足 ,采用分类讨论思想,如果 ,就是  次;否则设 ,试图找到一个位置 ,满足  and ,就是  次;否则,发现一个性质,任意位置 , 和  必有一个是 ,说明非  元素一定是单个单个存在的,且把数组分成若干段 ,总是得选择一段,一个个染色,那么结果就是 len(最短的一段)+2,或 min(t[0],t[1])+1 二者最小值
T3 是树状数组裸题,不记录了
3.22 美团 
T4 输入格式是 a(2)b(3)a(2) 这样表示字符和出现次数的,问最大切割的字段个数,满足 种类*长度>=k,直接贪心,能割就割,就是细节比较多
char a[N], s[N];
LL num[N];
LL cnt[30];
int len, i, j;
LL n, k;
    
    scanf("%lld%lld%s", &n, &k, a + 1);
    len = strlen(a + 1);
    for (i = 1, k = 1; i <= len; ) {
        if (a[i] >= 'a' && a[i] <= 'z') {
            s[j] = a[i];
            i += 2;
            if (i <= len) {
                LL tmp = 0;
                while (a[i] != '(') {
                    tmp = tmp * 10 + (a[i] - '0');
                    i ++;
                }
            }
            i ++;
            num[j ++] = tmp;
        } else {
            break;
        }
    }
    LL diff = 0, tmp = 0, res = 0;
    for (int i = 1; i < j; ) {
        // 没出现过
        if (cnt[s[i] - 'a'] == 0) {
            diff ++;
            cnt[s[i] - 'a'] ++;
        } 
        // 如果符合条件
        if ((tmp + num[i]) * diff >= k) {
            // 计算符合条件的最小数量
            LL sub = max(k / diff - tmp, 1LL);
            // 循环次数很少
            while ((tmp + sub) * diff < k) sub ++;
            num[i] -= sub;
            // 如果最小数量也用光了当前字母 移动到下一个字母
            if (num[i] == 0) i ++;
            // 若还有剩余 i 不变
            memset(cnt, 0, sizeof cnt);
            diff = tmp = 0;
            res ++;            
        } else {
            tmp += num[i];
            i ++;
        }
    }
    if (res == 0) puts("-1");
    else printf("%lld\n", res);T5 给定一棵树,每次询问求 走到 的概率,规定是不走回头路的,思路就是求 到 的路径,端点外每个点的 连乘,最后乘上 ,然后 就是概率,由于要输出 的这个 ,还要用逆元求一下,下面代码是用倍增,维护开区间 的 乘积,能跑样例但未经 oj 验证,感觉是对的(upd:已 ac)
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 200010;
const int MOD = 1e9 + 7;
int n, q, S, T;
vector<int> g[N];
int sz[N], d[N];
int p[N][21];
LL cnt[N][21];
void dfs(int u, int fa) {
    d[u] = d[fa] + 1;
    for (int v: g[u]) 
        if (v != fa) {
            p[v][0] = u;
            cnt[v][0] = 1;
            dfs(v, u);
        }
}
LL get(int s, int t) {
    if (d[s] > d[t]) swap(s, t);
    LL res = 1;
    int k = d[t] - d[s];
    for (int i = 0; i <= 20; i++) {
        if (k >> i & 1) {
            if (p[t][i] != s) {
                res = (res * cnt[t][i] % MOD * (sz[p[t][i]] - 1)) % MOD;
            } else {
                res = (res * cnt[t][i]) % MOD;
            }
            t = p[t][i];
        }
    }
    if (s == t) return res;
    for (int i = 20; i >= 0; i--) {
        int ps = p[s][i], pt = p[t][i];
        if (ps != pt) {
            res = (res * cnt[s][i] % MOD * (sz[ps] - 1) % MOD) * cnt[t][i] % MOD * (sz[pt] - 1) % MOD;
            s = ps, t = pt;
        }
    }
    return (res * (sz[p[s][0]] - 1)) % MOD;
}
LL qmi(LL x, LL p) {
    LL res = 1;
    while (p) {
        if (p & 1) res = (res * x) % MOD;
        x = x * x % MOD;
        p >>= 1;
    }
    return res;
}
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        g[x].push_back(y);
        g[y].push_back(x);
        sz[x] ++, sz[y] ++;
    }
    dfs(1, 0);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= 20; j++) {
            p[i][j] = p[p[i][j - 1]][j - 1];
            cnt[i][j] = (cnt[i][j - 1] * cnt[p[i][j - 1]][j - 1] % MOD * (sz[p[i][j - 1]] - 1)) % MOD;
        }
    
    scanf("%d", &q);
    while (q -- ) {
        scanf("%d%d", &S, &T);
        LL ans = get(S, T);
        printf("%lld\n", qmi(ans * sz[S] % MOD, MOD - 2));
    }
    return 0;
}
// 64 位输出请用 printf("%lld")3.17 米哈游 
T1 排序后贪心,正确性是因为考虑到每一个怪物时,当前的 cntE 都是满足刚好把它干掉的量。无脑累加 cntR,最后记得特判最后一个元素并更新 cntR 即可
void solve() {
    int n;
    cin >> n;
    vector<LL> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    LL E, R;
    cin >> E >> R;
    int cntE = 0, cntR = 0;
    sort(a.begin(), a.end());
    for (int i = 0; i < n; i++) {
        LL cur = a[i] - E * cntE - R * cntR;
        if (cur + cur <= a[i]) cntR ++;
        else {
            LL tmp = cur - a[i] / 2;
            cntE += (tmp + E - 1) / E;
            cntR ++;
        }
    }
    LL lst = a[n - 1] - E * cntE;
    if (lst - R * cntR > 0) cntE += (lst - R * cntR + E - 1) / E;
    else {
        int nR = (lst + R - 1) / R;
        cntR = min(cntR, nR);
    }
    cout << cntE << ' ' << cntR << endl;
}T3 定义 表示考虑前 台机,启动第 台机,此时左边最靠近的启动机是 的最大数量。转移时需要考虑 这样的三元组,满足 ,即只考虑 在某个范围内的那些 ,取最大的 。查询这个数,可以对每个 都记录一个离散化的权值树状数组,单点修改+区间查询即可
另一种思路:本质是寻找子序列 满足 ,可以用 表示以 结尾,差值为 的最大长度,则 ,其中 大于 ,这个就可以二分查找出来了(类似于 map 的思路,不直接维护所有差值,而是用 pair 记录出现了哪些差值),最后为了省略掉树状数组,每次求完 后直接求一个后缀最大值
    int n;
    cin >> n;
    vector<LL> a(n);
    vector<vector<PLI>> f(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        f[i].push_back({0x3f3f3f3f3f3f3f3f, 1});
    }
    int res = 1;
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
        // 寻找最小的 v 满足 f[j][v]>a[i]-a[j]
            int l = 0, r = f[j].size() - 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (f[j][mid].x > a[i] - a[j]) r = mid;
                else l = mid + 1;
            }
            f[i].push_back({a[i] - a[j], f[j][l].y + 1});
            res = max(res, f[j][l].y + 1);
        }
        sort(f[i].begin(), f[i].end());
        int sz = f[i].size();
        int mx = f[i][sz - 1].y;
        // 后缀最大值代替树状数组
        for (int j = f[i].size() - 1; j >= 0; j--) {
            mx = max(mx, f[i][j].y);
            f[i][j].y = mx;
        }
    }
    cout << res << endl;