Say 题选记(9.14 - 9.20)

P6619 [省选联考 2020 A/B 卷] 冰火战士

树状数组倍增板子。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 5;
#define lowbit(i) ((i) & (-(i)))
int a[2][N], n, _x[N], cnt, sum[2];
void add(int a[], int x, int k){
    for(int i = x; i <= cnt; i += lowbit(i))
        a[i] += k;
}
int query(int a[], int x){
    int sum = 0;
    for(int i = x; i; i -= lowbit(i)) sum += a[i];
    return sum;
}
struct node{
    int op, idx, x, y;
}Op[N];
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    for(int i = 1; i <= n; ++i){
        cin >> Op[i].op >> Op[i].idx;
        if(Op[i].op == 1){
            cin >> Op[i].x >> Op[i].y;
            _x[++cnt] = Op[i].x;
        }
        else{
            Op[i].op = -1;
            int idx = Op[i].idx;
            Op[i].x = Op[idx].x;
            Op[i].y = Op[idx].y;
            Op[i].idx = Op[idx].idx;
        }
    }
    sort(_x + 1, _x + 1 + cnt);
    cnt = unique(_x + 1, _x + 1 + cnt) - _x - 1;
    for(int i = 1; i <= n; ++i){
        Op[i].x = lower_bound(_x + 1, _x + 1 + cnt, Op[i].x) - _x;
        int x = Op[i].x, y = Op[i].y, op = Op[i].op, idx = Op[i].idx;
        add(a[idx], x + idx, op * y);
        sum[idx] += op * y;
        int nw0 = 0, nw1 = sum[1], p = 0;
        for(int j = 20; j >= 0; --j){
            int nxtp = p + (1 << j);
            if(nxtp <= cnt && nw0 + a[0][nxtp] <= nw1 - a[1][nxtp]){
                nw0 += a[0][nxtp], nw1 -= a[1][nxtp];
                p = nxtp;
            }
        }
        int ans1 = nw0, ans2 = 0, p2 = 0;
        if(p < cnt){
            ans2 = sum[1] - query(a[1], p + 1);
            int nw = sum[1];
            for(int j = 20; j >= 0; --j){
                int nxtp = p2 + (1 << j);
                if(nxtp <= cnt && nw - a[1][nxtp] >= ans2){
                    nw -= a[1][nxtp], p2 = nxtp;
                }
            }
        }
        if(max(ans1, ans2) == 0) cout << "Peace\n";
        else{
            if(ans1 > ans2) cout << _x[p] << ' ' << 2 * ans1 << '\n';
            else cout << _x[p2] << ' ' << 2 * ans2 << '\n';
        }
    }
    return 0;
}

P4768 [NOI2018] 归程

Kruskal 重构树板子,注意求的是最大最小瓶颈路还是最小最大瓶颈路(可能这么叫?

Code
using namespace std;
typedef pair<int, int> pii;
const int N = 4e5 + 5;
struct edge{
    int u, v, w, f, pre;
}e[N * 2];
int n, m, head[N], fa[N], cnt, dis[N], val[N], mn[N], st[N][21];
vector<int> g[N];
void adde(int u, int v, int w, int f){
    e[++cnt] = {u, v, w, f, head[u]};
    head[u] = cnt;
}
void dij(){
    priority_queue<pii, vector<pii>, greater<pii> > q;
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    q.emplace(0, 1);
    while(!q.empty()){
        auto [dist, u] = q.top();
        q.pop();
        if(dist > dis[u]) continue;
        for(int k = head[u]; k; k = e[k].pre){
            int v = e[k].v, w = e[k].w;
            if(dist + w < dis[v]){
                dis[v] = dist + w;
                q.emplace(dis[v], v);
            }
        }
    }
}
int getf(int u){ return u == fa[u] ? u : fa[u] = getf(fa[u]); }
void dfs(int u){
    mn[u] = dis[u];
    for(int i = 1; i <= 19; ++i) st[u][i] = st[st[u][i - 1]][i - 1];
    for(int v : g[u]){
        st[v][0] = u;
        dfs(v);
        mn[u] = min(mn[v], mn[u]);
    }
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    int T;
    cin >> T;
    while(T--){
        memset(head, 0, sizeof(head));
        memset(g, 0, sizeof(g));
        memset(mn, 0x3f, sizeof(mn));
        cnt = 0;
        cin >> n >> m;
        for(int i = 1; i <= m; ++i){
            int u, v, w, f;
            cin >> u >> v >> w >> f;
            adde(u, v, w, f), adde(v, u, w, f);
        }
        dij();
        // for(int i = 1; i <= n; ++i) cout << dis[i] << '\n';
        for(int i = 1; i <= 2 * n; ++i) fa[i] = i;
        sort(e + 1, e + 1 + cnt, [](edge x, edge y){
            return x.f > y.f;
        });
        int tot = n;
        for(int i = 1; i <= cnt; ++i){
            int u = e[i].u, v = e[i].v, f = e[i].f;
            int fu = getf(u), fv = getf(v);
            if(fu != fv){
                fa[fu] = fa[fv] = ++tot;
                g[tot].emplace_back(fu);
                g[tot].emplace_back(fv);
                val[tot] = f;
            }
            if(tot - n == n - 1) break;
        }
        st[tot][0] = 0;
        dfs(tot);
        int lst = 0, q, k, s;
        cin >> q >> k >> s;
        while(q--){
            int v, p;
            cin >> v >> p;
            v = (v + lst * k - 1) % n + 1;
            p = (p + lst * k) % (s + 1);
            for(int j = 19; j >= 0; --j){
                if(val[st[v][j]] > p) v = st[v][j];
            }
            cout << (lst = mn[v]) << '\n';
        }
    }
    return 0;
}

P9870 [NOIP2023] 双序列拓展

dp 很 trival,然后变成了一个网格图上有障碍点查询联通性的问题。
主要的思想就是总是考虑限制最强的地方,神来之笔,把 \(y_j\) 套上一个 \(\min\) 之后居然就有单调性了,然后就可以双指针做。
感觉第二篇题解是更自然的思路,但写的时候看的是第一篇题解(好像就是把双指针倒过来写了)。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 5;
typedef array<int, N> arr;
arr a, b, pamn, pbmn, pamx, pbmx, samn, samx, sbmn, sbmx, ta, tb;
bool check1(const arr&a, const arr &b, int x, int y){
    if(x == 1 || y == 1) return 1;
    if(a[pamn[x - 1]] < b[pbmn[y - 1]]) return check1(a, b, pamn[x - 1], y);
    else if(a[pamx[x - 1]] < b[pbmx[y - 1]]) return check1(a, b, x, pbmx[y - 1]);
    return 0;
}
bool check2(const arr&a, const arr &b, int x, int y, int enx, int eny){
    if(x == enx || y == eny) return 1;
    if(a[samn[x + 1]] < b[sbmn[y + 1]]) return check2(a, b, samn[x + 1], y, enx, eny);
    else if(a[samx[x + 1]] < b[sbmx[y + 1]]) return check2(a, b, x, sbmx[y + 1], enx, eny);
    return 0;
}
bool solve(const arr &a, const arr &b, int n, int m){
    auto get = [](const arr &x, int nw, int i, int fl){
        if(x[nw] * fl > x[i] * fl) return i;
        return nw;
    };
    
    pamn[1] = pamx[1] = pbmn[1] = pbmx[1] = 1;
    for(int i = 2; i <= n; ++i) pamn[i] = get(a, pamn[i - 1], i, 1), pamx[i] = get(a, pamx[i - 1], i, -1);
    for(int i = 2; i <= m; ++i) pbmn[i] = get(b, pbmn[i - 1], i, 1), pbmx[i] = get(b, pbmx[i - 1], i, -1);
    samn[n] = samx[n] = n, sbmn[m] = sbmx[m] = m;
    for(int i = n - 1; i >= 1; --i) samn[i] = get(a, samn[i + 1], i, 1), samx[i] = get(a, samx[i + 1], i, -1);
    for(int i = m - 1; i >= 1; --i) sbmn[i] = get(b, sbmn[i + 1], i, 1), sbmx[i] = get(b, sbmx[i + 1], i, -1);

    // for(int i = 1; i <= n; ++i) cout << pamn[i] << ' ' << samn[i] << ' ' << pamx[i] << ' ' << samx[i] << '\n';
    // for(int i = 1; i <= m; ++i) cout << pbmn[i] << ' ' << sbmn[i] << ' ' << pbmx[i] << ' ' << sbmx[i] << '\n';

    if(a[pamx[n]] >= b[pbmx[m]] || a[pamn[n]] >= b[pbmn[m]]) return 0;
    return check1(a, b, pamn[n], pbmx[m]) && check2(a, b, pamn[n], pbmx[m], n, m);
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    int c, q, n, m;
    cin >> c >> n >> m >> q;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    for(int i = 1; i <= m; ++i) cin >> b[i];
    cout << (solve(a, b, n, m) || solve(b, a, m, n) ? '1' : '0');
    while(q--){
        int kx, ky;
        cin >> kx >> ky;
        ta = a, tb = b;
        while(kx--){
            int p, v; cin >> p >> v;
            ta[p] = v;
        }
        while(ky--){
            int p, v; cin >> p >> v;
            tb[p] = v;
        }
        // for(int i = 1; i <= n; ++i) cout << ta[i] << ' ';
        // for(int j = 1; j <= m; ++j) cout << tb[j] << ' ';
        // cout << '\n';
        cout << (solve(ta, tb, n, m) || solve(tb, ta, m, n) ? '1' : '0');
    }
    return 0;
}

P7737 [NOI2021] 庆典

挺综合的一道题。
一开始看到这个 "对于三座城市 \(x\)\(y\)\(z\),若 \(x\Rightarrow z\)\(y\Rightarrow z\),那么有 \(x\Rightarrow y\)\(y\Rightarrow x\)" 性质的时候很疑惑,作用是在缩成 DAG 之后还可以进一步缩成树,注意下。
树上拓扑序就好处理多了,限制可以轻松地表示为在不在 \(u\) 的子树内。
考虑询问,发现暴力每次都跑一遍 dfs 求可不可达很亏,实际上只需要用建虚树的办法把关键点找出来建图跑就行,这样跑一次就是 \(O(k)\) 的了。
建虚树本身就是很有用的过程。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
int n, m, q, k;
vector<int> g[N], e[N], tmp[N];
int dfn[N], low[N], scc[N], cnt, tsp, st[N], tp, f[N][21], lg[N], siz[N], a[N], r, dis[N];
bitset<N> in, vis, vise, bk;
struct edge{
    int u, v, w, pre;
}s[N], _s[N];
int sh[N], tot, _sh[N], _tot;
struct node{
    int u, v;
}add[5];
void adde(int u, int v, int w){
    s[++tot] = {u, v, w, sh[u]};
    sh[u] = tot;
    _s[++_tot] = {v, u, w, _sh[v]};
    _sh[v] = _tot;
    vise[tot] = 0;
}

void tarjan(int u){
    dfn[u] = low[u] = ++tsp;
    in[st[++tp] = u] = 1;
    for(int v : g[u]){
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(in[v]) low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u]){
        ++cnt;
        while(1){
            int x = st[tp];
            in[x] = 0, scc[x] = cnt, siz[cnt]++;
            --tp;
            if(x == u) break; 
        }
    }
}
void dfs(int u, int fa){
    if(in[u]) return;
    in[u] = 1;
    if(fa) e[fa].emplace_back(u);
    for(int v : tmp[u]) dfs(v, u);
}

int get(int u, int v){
    return (dfn[u] < dfn[v] ? u : v);
}
void init(int u, int fa){
    dis[u] = dis[fa] + siz[u];
    f[dfn[u] = ++tsp][0] = fa;
    for(int v : e[u]) init(v, u);
}
int Lca(int u, int v){
    if(u == v) return u;
    if(dfn[u] > dfn[v]) swap(u, v);
    u = dfn[u] + 1, v = dfn[v];
    int d = lg[v - u + 1];
    return get(f[u][d], f[v - (1 << d) + 1][d]); 
}

void build(){
    sort(a + 1, a + 1 + r, [](int x, int y){
            return dfn[x] < dfn[y];
    });
    r = unique(a + 1, a + 1 + r) - a - 1;
    _tot = tot = tp = 0;
    st[++tp] = cnt;
    sh[cnt] = _sh[cnt] = 0;
    auto len = [](int x, int y){ return dis[f[dfn[y]][0]] - dis[x];  };
    for(int i = 1; i <= r; ++i){
        if(a[i] == cnt) continue;
        int l = Lca(a[i], st[tp]);
        if(l != st[tp]){
            while(dfn[l] < dfn[st[tp - 1]]){
                adde(st[tp - 1], st[tp], len(st[tp - 1], st[tp]));
                --tp;
            }
            if(l != st[tp - 1]) sh[l] = _sh[l] = 0, adde(l, st[tp], len(l, st[tp])), st[tp] = l;
            else adde(st[tp - 1], st[tp], len(st[tp - 1], st[tp])), --tp;
        }
        sh[a[i]] = _sh[a[i]] = 0;
        st[++tp] = a[i];
    }
    for(int i = 1; i < tp; ++i) adde(st[i], st[i + 1], len(st[i], st[i + 1]));
}
void clear(int u){
    vis[u] = bk[u] = 0;
    for(int k = sh[u]; k; k = s[k].pre){
        clear(s[k].v);
    }
}
int ans = 0;
void get(int u){
    if(vis[u]) return;
    vis[u] = 1;
    for(int k = sh[u]; k; k = s[k].pre) vise[k] = 1, get(s[k].v);
}
void rev(int u){
    if(bk[u]) return;
    bk[u] = 1;
    if(vis[u]) ans += siz[u];
    for(int k = _sh[u]; k; k = _s[k].pre){
        if(vise[k]) ans += _s[k].w;
        rev(_s[k].v);
    } 
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> m >> q >> k;
    for(int i = 0; (1 << i) <= n; ++i) lg[(1 << i)] = i;
    for(int i = 1; i <= m; ++i){
        int u, v;
        cin >> u >> v;
        g[u].emplace_back(v);
    }

    for(int i = 1; i <= n; ++i){
        if(!lg[i]) lg[i] = lg[i - 1];
        if(!dfn[i]) tarjan(i);
    }
    in.reset();
    for(int i = 1; i <= n; ++i){
        for(int v : g[i]){
            if(scc[i] != scc[v]) tmp[scc[i]].emplace_back(scc[v]);
        }
    }
    for(int i = 1; i <= cnt; ++i) sort(tmp[i].begin(), tmp[i].end(), greater<int>());
    dfs(cnt, 0);
    memset(dfn, 0, sizeof(dfn));
    tsp = 0;
    init(cnt, 0);
    for(int j = 1; (1 << j) <= cnt; ++j){
        for(int i = 1; i + (1 << j) - 1 <= cnt; ++i){
            f[i][j] = get(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }

    while(q--){
        int s, t;
        cin >> s >> t;
        r = 0;
        a[++r] = scc[s], a[++r] = scc[t];
        for(int i = 1; i <= k; ++i){
            int u, v; cin >> u >> v;
            u = scc[u], v = scc[v];
            add[i] = {u, v};
            a[++r] = u, a[++r] = v;
        }
        build();
        clear(cnt);
        for(int i = 1; i <= k; ++i){
            if(add[i].u != add[i].v) adde(add[i].u, add[i].v, 0);
        }
        ans = 0;
        get(scc[s]);
        rev(scc[t]);
        cout << ans << '\n';
    }
    return 0;
} 

P5290 [十二省联考 2019] 春节十二响

先从特殊的想起,二叉的情况是 trival 的,那么多叉的就自然可做了。
启发式合并貌似并非难点(?

Code
#include <bits/extc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
typedef __gnu_pbds::priority_queue<ll> qp;
qp q[N], tmp;
vector<int> e[N];
ll a[N];
int n;
void merge(qp &x, qp &y){
    if(x.size() < y.size()) x.swap(y); 
    while(!y.empty()){
        tmp.push(max(x.top(), y.top()));
        x.pop(), y.pop();
    }
    x.join(tmp);
}
void dfs(int u){
    for(int v : e[u]){
        dfs(v);
        merge(q[u], q[v]);
    }
    q[u].push(a[u]);
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    for(int i = 1; i <= n; ++i) 
        cin >> a[i];
    for(int i = 2; i <= n; ++i){
        int x;
        cin >> x;
        e[x].emplace_back(i);
    }
    dfs(1);
    ll ans = 0;
    while(!q[1].empty()) ans += q[1].top(), q[1].pop();
    cout << ans;
    return 0;
}

P7515 [省选联考 2021 A 卷] 矩阵游戏

构造题往往条件是较松的,可以先固定第一行第一列,发现 \(a\) 就确定了。但是此时并不一定满足 \(0 \le a_{i, j} \le 10^6\)
考虑调整,由于第一行第一列决定了整个矩阵,我们只要找到合法的第一行第一列即可。发现能进行的调整总是对一整行一整列一加一减,就变成差分约束了。
注意符号,要不然就会变成和分约束。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 605, V = 1e6;
#define c(i) (i)
#define d(j) ((j) + n)
int n, m, cnt = 0;
ll a[N][N], b[N][N], dis[N], e[N][N];
void solve(){
    cnt = 0;
    cin >> n >> m;
    for(int i = 1; i <= n - 1; ++i){
        for(int j = 1; j <= m - 1; ++j) cin >> b[i][j];
    }
    for(int i = 1; i <= n; ++i) a[1][i] = a[i][1] = 0;
    for(int i = 2; i <= n; ++i){
        for(int j = 2; j <= m; ++j)
            a[i][j] = b[i - 1][j - 1] - a[i - 1][j] - a[i][j - 1] - a[i - 1][j - 1];
    }
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j){
            int u = c(i), v = d(j);
            if((i + j) & 1) swap(u, v);
            e[u][v] = a[i][j];
            e[v][u] = V - a[i][j];
        }
    }
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    bool fl = 0;
    for(int i = 1; i <= n + m; ++i){
        fl = 0;
        for(int u = 1; u <= n + m; ++u){
            int l = 1, r = n + m;
            u > n ? r = n : l = n + 1;
            for(int v = l; v <= r; ++v){
                ll d;
                dis[v] > (d = dis[u] + e[u][v]) ? fl = 1, dis[v] = d : 0; 
            }
        }
        if(!fl) break;
    }
    if(fl) return cout << "NO\n", void();
    cout << "YES\n";
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j){
            ll delta = dis[c(i)] - dis[d(j)];
            if((i + j) & 1) delta *= -1;
            cout << a[i][j] + delta << ' ';
        }
        cout << '\n';
    }
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    int T;
    cin >> T;
    while(T--) solve();
    return 0;
}

P6000 [CEOI 2016] match

判断整个字符串是否合法就跟判断括号序列类似,用栈即可。
如果原序列合法,那么字典序最小的方案如何构造呢?一个贪心,就是让与当前左括号匹配的右括号尽可能靠后。如何模拟这个贪心呢,先考虑第一次,哪个位置 \(i\) 作为右括号是合法的呢?实际上只需要保证 \([i + 1, n]\) 是合法的并且 \(a_i = a_1\) 即可。这是因为,我们本来需要保证 \([1,i]\)\([i + 1, n]\) 都是合法的,但是由于 \([1, n]\) 已经是合法的了,保证了其中一个另一个就自然满足了。之后几次的操作就是递归到子问题。
那么如何每次找到最大合法的右括号呢?考虑 \(dp_{r, x}\) 表示 \(a_i = x\) 并且 \([i + 1, r]\) 是合法的最大的 \(i\)。有转移 \(dp_{r, x} = dp_{dp_{r - 1, a_r} - 1, x}\),和边界 \(dp_{r, a_r} = r\)。(这个转移思路来自括号序列的性质 \(A\) 合法,\(B\) 合法,那么 \(AB\) 合法。)
输出方案时,dfs 即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int st[N], tp, dp[N][26], n, s[N];
bitset<N> ans;
string a;
void dfs(int l, int r){
    if(l > r) return;
    int x = dp[r][s[l]]; 
    ans[l] = 0, ans[x] = 1;
    dfs(l + 1, x - 1);
    dfs(x + 1, r);
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> a; n = a.size();
    for(int i = 0; i < n; ++i) s[i + 1] = a[i] - 'a';
    for(int i = 1; i <= n; ++i){
        if(tp && st[tp] == s[i]) --tp;
        else st[++tp] = s[i];
    }
    if(tp) return cout << -1, 0;
    for(int r = 1; r <= n; ++r){
        for(int j = 0; j < 26; ++j){
            int pre = dp[r - 1][s[r]];
            if(pre) dp[r][j] = dp[pre - 1][j];
        }
        dp[r][s[r]] = r;
    }
    dfs(1, n);
    for(int i = 1; i <= n; ++i) cout << (ans[i] ? ')' : '(');
    return 0;
}

P7516 [省选联考 2021 A/B 卷] 图函数

对于这种从小到大删点/删边,考察连通性的问题,总是有等价瓶颈路的转化,本题综合考察了这两种 trick。
先不考虑删边,对单个图点对 \((i,j)(i \le j)\) 有贡献,等价于能从 \(i\) 只经过编号大于等于 \(i\) 能到达 \(j\),并且从 \(j\) 只经过编号大于等于 \(i\) 能到达 \(i\)
那对于多个图要删边的话,也有类似转换。首先,定义一条路径 \(P\)\(W(P)\) 为经过所有的边编号的最小值。记 \(e_{i,j}\) 表示从 \(i\)\(j\) 只经过大于等于 \(\min(i,j)\) 的点的所有路径 \(P\) 的最大 \(W(P)\),那么这个点对对于 \(h(G_i)(i \ge W(P))\) 都不会有贡献,那么就做完了。具体来说,\(e_{i,j}\) 可以将边权赋为编号之后,从大到小枚举中转点 floyd 求出。
卡常,由于每次总是枚举中转点 \(k\) 和更大的一个 \(j\) 来统计答案,那么再更新完 \(k\) 之后,大于 \(k\) 的点之间就没有必要再计算了,这样可以少一半常数。
这个 trick 也有反过来用的题目,比如这道 P2502 [HAOI2006] 旅行

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 5;
int ans[200005], e[N][N], n, m;
#define Tp template<typename T>
#define Ts template<typename T,typename... _T>
char buf[1<<20],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
Tp inline void read(T& x){
    x=0;char c=getchar();bool f=0;
    for(;!isdigit(c);c=getchar())if(c=='-')f=1;
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    f&&(x=-x);
}
Ts inline void read(T& x,_T&... y){read(x),read(y...);}
int main(){
    read(n, m);
    for(int i = 1; i <= m; ++i){
        int u, v;
        read(u, v);
        e[u][v] = i;    
    }
    for(int i = 1; i <= n; ++i) e[i][i] = m + 1;
    for(int k = n; k >= 1; --k){
        for(int i = 1; i <= n; ++i){
            int tmp = e[i][k];
            if(!tmp) continue; 
            for(int j = 1; j <= (i >= k ? k - 1 : n); ++j){
                e[i][j] = max(e[i][j], min(tmp, e[k][j]));
            }
        }
        for(int j = k; j <= n; ++j){
            int idx = min(e[k][j], e[j][k]);
            ans[idx]--;
        }
    }
    ans[0] += (n * (n + 1)) >> 1;
    printf("%d ", ans[0]);
    for(int i = 1; i <= m; ++i){
        ans[i] += ans[i - 1];
        printf("%d ", ans[i]);
    }
    return 0;
}

P10142 [USACO24JAN] Mooball Teams III P

组合问题居然也能线段树来解决。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5, mod = 1e9 + 7;
int n, x[N], y[N], id[N];
ll pw[N], ans;
ll mul(ll x, ll y){ return x * y % mod; }
void add(ll &x, ll y){ (x += y) %= mod, (x += mod) % mod; }
#define ls(p) p << 1
#define rs(p) p << 1 | 1
struct node{
    ll pwr, pwb, rb, br;
}tr[N << 2];
node merge(node x, node y){
    return node{mul(x.pwr, y.pwr), mul(x.pwb, y.pwb), 
        (mul(x.pwr, y.rb) + mul(x.rb, y.pwb) - mul(x.pwr, y.pwb)) % mod,
        (mul(x.pwb, y.br) + mul(x.br, y.pwr) - mul(x.pwb, y.pwr)) % mod};
}
void update(int p, int pl, int pr, int x, int k){
    if(pl == pr) return tr[p] = {1 + (k == 0), 1 + k, 2, 2}, void();
    int mid = (pl + pr) >> 1;
    if(x <= mid) update(ls(p), pl, mid, x, k);
    else update(rs(p), mid + 1, pr, x, k);
    tr[p] = merge(tr[ls(p)], tr[rs(p)]);
}
node query(int p, int pl, int pr, int L, int R){
    if(L > R) return node{1, 1, 1, 1};
    if(L <= pl && R >= pr) return tr[p];
    int mid = (pl + pr) >> 1;
    if(L <= mid && R > mid) return merge(query(ls(p), pl, mid, L, R), query(rs(p), mid + 1, pr, L, R));
    if(L <= mid) return query(ls(p), pl, mid, L, R);
    if(R > mid) return query(rs(p), mid + 1, pr, L, R);
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    pw[0] = 1;
    for(int i = 1; i <= n; ++i){
        id[i] = i;
        pw[i] = mul(2, pw[i - 1]);
        cin >> x[i] >> y[i];
    }
    sort(id + 1, id + 1 + n, [](int s, int t){
        return x[s] < x[t];
    });
    for(int i = 1; i <= n; ++i){ 
        update(1, 1, n, i, 1); 
        add(ans, mul(2, mul(pw[i - 1], pw[n - i] - 1)));
    }
    for(int it = 1; it <= n; ++it){
        int i = id[it];
        node a = query(1, 1, n, 1, y[i] - 1), b = query(1, 1, n, y[i] + 1, n);
        add(ans, -(mul(a.pwr, b.rb - b.pwr) + mul(b.pwr, a.br - a.pwr)) % mod);
        update(1, 1, n, y[i], 0);
    }
    cout << mul(ans, 2);
    return 0;
}

P6845 [CEOI 2019] Dynamic Diameter

欧拉序的应用。
直径 \(d = \max_{x,y}\{dep_x + dep_y - 2dep_{lca(x,y)}\}\)。考虑在欧拉序序列上解决这一问题,等价于要求 \(\max_{i \le j}\{dep_{eul_i} + dep_{eul_j} - 2 \times \min_{ i \le k \le j} \{dep_k\} \}\)。发现这个 \(\min\) 直接可以丢掉,所以就等价于维护一个序列 \(a\)\(\max_{i \le k \le j}\{a_i + a_j - 2 \times a_k\}\)。具体来说,维护 \(ans\)\(lp = \max_{l \le k \le x \le r}\{a_x - 2 \times a_k\}\)\(rp = \max_{l \le x \le k \le r}\{a_x - 2 \times a_k\}\)\(mx\)\(mn\) 就可以了。注意一下 pushup 时 \(lp \gets \max\{lp_{ls}, lp_{rs}, mx_{rs} - 2 \times mn_{ls}\}\)\(rp\) 同理。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef tuple<int, ll, int> tpi;
const int N = 1e5 + 5;
#define ls(p) p << 1
#define rs(p) p << 1 | 1
struct edge{
    int u, v; ll w;
}Edge[N];
vector<tpi> e[N];
int eul[N << 1], fst[N], lst[N], tot, n, q; 
ll mx[N << 3], mn[N << 3], lp[N << 3], rp[N << 3], ans[N << 3], tag[N << 3], dep[N], w;
void pushup(int p){
    mx[p] = max(mx[ls(p)], mx[rs(p)]);
    mn[p] = min(mn[ls(p)], mn[rs(p)]);
    lp[p] = max({lp[ls(p)], lp[rs(p)], mx[rs(p)] - 2 * mn[ls(p)]});
    rp[p] = max({rp[ls(p)], rp[rs(p)], mx[ls(p)] - 2 * mn[rs(p)]});
    ans[p] = max({ans[ls(p)], ans[rs(p)], mx[ls(p)] + lp[rs(p)], mx[rs(p)] + rp[ls(p)]});
}
void addtag(int p, ll k){
    tag[p] += k;
    mx[p] += k, mn[p] += k;
    lp[p] -= k, rp[p] -= k;
}
void pushdown(int p){
    if(tag[p]){
        addtag(ls(p), tag[p]);
        addtag(rs(p), tag[p]);
        tag[p] = 0;
    }
}
void upd(const int &L, const int &R, const ll &k, int p = 1, int pl = 1, int pr = tot){
    if(L <= pl && R >= pr) return addtag(p, k), void();
    int mid = (pl + pr) >> 1;
    pushdown(p);
    if(L <= mid) upd(L, R, k, ls(p), pl, mid);
    if(R > mid) upd(L, R, k, rs(p), mid + 1, pr);
    pushup(p);
}
void build(int p = 1, int pl = 1, int pr = tot){
    if(pl == pr) return lp[p] = rp[p] = -dep[eul[pl]], mx[p] = mn[p] = dep[eul[pl]], void();
    int mid = (pl + pr) >> 1;
    build(ls(p), pl, mid);
    build(rs(p), mid + 1, pr);
    pushup(p);
}
void dfs(int u, int fa){
    eul[fst[u] = ++tot] = u;
    for(auto [v, w, k] : e[u]){
        if(v != fa){
            Edge[k] = {u, v, w};
            dep[v] = dep[u] + w;
            dfs(v, u);
            eul[++tot] = u;
        }
    }
    lst[u] = tot;
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    ll lstans = 0;
    cin >> n >> q >> w;
    for(int i = 1; i < n; ++i){
        int u, v; ll w;
        cin >> u >> v >> w;
        e[u].emplace_back(v, w, i);
        e[v].emplace_back(u, w, i);
    }
    dfs(1, 0);
    build();
    for(int i = 1; i <= q; ++i){
        int d; ll e;
        cin >> d >> e;
        d = (d + lstans) % (n - 1) + 1;
        e = (e + lstans) % w;
        auto &[u, v, ew] = Edge[d];
        upd(fst[v], lst[v], e - ew);
        ew = e;
        cout << (lstans = ans[1]) << '\n'; 
    }
    return 0;
}

P4492 [HAOI2018] 苹果树

要求的是所有树的树上点对距离之和。
转换计数对象,考虑边的贡献,一条边 \((u, fa_u)\) 会被贡献 \(siz_u \times (n - siz_u)\) 次。那么就只需要计数几种生成出来的树能使得 \(u\) 的子树大小为 \(siz_u\)
貌似用生成函数可以做到 log。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3 + 5;
int mod, n;
ll c[N][N], fac[N];
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> mod;
    c[0][0] = fac[0] = 1;
    for(int i = 1; i <= n; ++i){
        c[i][0] = 1;
        fac[i] = (fac[i - 1] * i) % mod;
        for(int j = 1; j <= i; ++j){
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
        }
    }
    ll ans = 0;
    for(int i = 2; i <= n; ++i){
        for(int j = 1; j <= n - i + 1; ++j){
            (ans += c[n - i][j - 1] * fac[n - j - 1] % mod * fac[j] % mod * i % mod * (i - 1)
                * j % mod * (n - j) % mod) %= mod; 
        }
    }
    cout << ans;
    return 0;
}
posted @ 2025-09-16 23:39  Hengsber  阅读(11)  评论(0)    收藏  举报