2025杭电第5场

写一下学到了新东西的几个题目,数学太差所以很简单的数学也不会

1001

树上笛卡尔树+倍增
对于大根堆笛卡尔树(后称为新树)而言,子树内的点都可以吃掉,所以如果能做出这么一个结构,对每个查询x,就只需要找到新树上x到根的路径上最深的\(a[fa[x]]-sum[x]>y\)的点对应的x,答案为\(sum[x]+y\)

  1. 建树方法:
    类似点分治,但是可以直接并查集。
    按照点权从小到大排序,每次加入的点就必然是当前子树的最大值(根),维护父子节点信息即可。
    (其实树上笛卡尔树就是一直给子树换根,根为子树内的最大值的一个做法)
  2. 倍增维护:
    考虑到所求内容并非单调,但发现我们只关心最大值和y的关系,所以维护最大值就有单调性可以倍增。
  3. 代码里还学了新的排序方法不用写struct了,处理倍增也不用递归了
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define pii pair<int, int>
const int N = 1e5 + 7, INF = 1e9+7, MOD = 998244353;
int n, m, fa[N][22], rt, id[N];
ll y, q, sum[N], mx[N][22], a[N], b[N];
int ff[N];
bool vis[N];
bool cmp(const ll &x, const ll &y) {
    return a[x] < a[y];
}
vector<int> E[N];
struct DSU{
    int find(int x){return (x == ff[x]? x : ff[x]= find(ff[x]));}
}dsu;
void init() {
    a[0] = 1e17;
    for (int i = 1; i <= n; i++) {
        vis[i] = sum[i] = 0, ff[i] = i, fa[i][0] = 0;
        E[i].clear();
    }
    for (int i = 0; i <= 19; i++) mx[0][i] = 1e17;
}
void solve() { 
    cin >> n >> q;
    init();
    for (int i = 1; i <= n; i++) 
        cin >> a[i], id[i] = i;
    for (int i = 1; i <= n; i++) 
        cin >> b[i];
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        E[u].push_back(v); E[v].push_back(u);
    }
    sort(id + 1, id + n + 1, cmp);
    rt = id[n];
    for (int i = 1; i <= n; i++) {
        sum[id[i]] = b[id[i]];
        for (auto j : E[id[i]]) {
            if (vis[j]) {
                int f = dsu.find(j);
                ff[f] = id[i];
                fa[f][0] = id[i];
                sum[id[i]] += sum[f];
                mx[f][0] = a[id[i]] - sum[f];
            }
        }
        vis[id[i]] = 1;
    }
    fa[rt][0] = 0;
    for (int j = 1; (1 << j) <= n; j++)
        for (int i = 1; i <= n; i++) {
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
            mx[i][j] = max(mx[i][j - 1], mx[fa[i][j - 1]][j - 1]);
        }
    while(q--) {
        int x;
        cin >> x >> y;
        if (y >= a[x]) {
            for (int i = 19; i >= 0; i--) {
                if (fa[x][i] && mx[x][i] <= y) {
                    x = fa[x][i];
                }
            }
            cout << sum[x] + y << endl;
        }
        else cout << y << endl;
    }
    init();
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T = 1;
    cin >> T;
    while(T--) {
        solve();
    }
}

1007

组合数学推式子
枚举mex可得所求内容为:\(\sum_{mex=0}^{k}mex\frac{C_{n-mex}^{k-mex}}{C_n^k}\)
因为组合数带因数求和无法计算(佬说的)所以想到拆分成mex个组合数相加
然后分为k+1层,因为\(C_{n-mex}^{k-mex}=C_{n-mex}^{n-k}\),对于相邻mex底数差1,上面的数相同,所以可以求和为一个组合数
对于若干层也可以用这样的方式求和,最后结果是两个组合数相除。

1008

AC自动机
好像有人用sam做,但是我只会ac自动机。
首先要对前缀和后缀进行减枝,这样trie树上每个节点要么不被接受,要么只有一个可接受状态。
用ac自动机做的话无法避免会算重的问题,可以通过在后缀ac自动机上跑所有前缀字符串来减去算重的部分。
这题暴力跳fail边不会t,大概是因为题目不能支持拓扑排序优化

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 20;
int l, r;
string pre[N], suf[N], ss;
struct AC{
    int t[N][26], fail[N], tot, len[N], id[N];
    ll mat[N];
    bool vis[N];
    void init() {
        for (int i = 0; i <= tot + 10; i++) {
            for (int j = 0; j < 26; j++) t[i][j] = 0;
            fail[i] = len[i] = id[i] = 0;
        }
        tot = 0;
    }
    void build_trie(string s, int idx){
        int p = 0;
        for (int i = 0; i < s.size(); i++) {
            int c = s[i] - 'a';
            if (t[p][c]) p = t[p][c];
            else t[p][c] = ++tot, p = tot;
        }
        // cout << s << ' ' << p <<" ???"<< endl;
        len[p] = s.size();
        id[p] = idx;
    }
    void build_AC() {
        queue<int> q;
        for (int i = 0; i < 26; i++) {
            if (t[0][i]) q.push(t[0][i]);
        }
        while(!q.empty()) {
            int p = q.front();
            q.pop();
            for (int i = 0; i < 26; i++) {
                if (t[p][i]) {
                    fail[t[p][i]] = t[fail[p]][i];
                    q.push(t[p][i]);
                }
                else t[p][i] = t[fail[p]][i];
            }
        }
    }
    void rebuild_trie() {
        queue<int> q;
        for (int i = 0; i < 26; i++) 
            if (t[0][i]) q.push(t[0][i]);
        while(!q.empty()) {
            int p = q.front();
            q.pop();
            if (id[p]) {
                vis[id[p]] = 1;
                continue;
            }
            for (int i = 0; i < 26; i++) 
                if (t[p][i]) q.push(t[p][i]);
        }
        init();
    }
    int que(string s) {
        int res = 0;
        int u = 0;
        for (int i = 0; i < s.size() - 1; i++) {
            u = t[u][s[i] - 'a'];
            for (int j = u; j; j = fail[j]) {
                if (len[j]) res++;
            }
        }
        return res;
    }
    void quep(string s){
        int u = 0;
        for (int i = 0; i <= s.size(); i++) mat[i] = 0;
        for (int i = 0; i < s.size(); i++) {
            u = t[u][s[i] - 'a'];
            for (int j = u; j; j = fail[j]) {
                if (len[j]) mat[i - len[j] + 1] = id[j];
            }
        }
    }void ques(string s){
        int u = 0;
        for (int i = 0; i <= s.size(); i++) mat[i] = 0;
        for (int i = 0; i < s.size(); i++) {
            u = t[u][s[i] - 'a'];
            for (int j = u; j; j = fail[j]) {
                if (len[j]) mat[i - len[j] + 1]++;
            }
        }
    }
}p, s;
void solve() {
    cin >> l >> r;
    for (int i = 0; i <= max(l, r); i++) s.vis[i] = p.vis[i] = 0;
    p.init(); s.init();
    for (int i = 1; i <= l; i++) {
        cin >> pre[i];
        p.build_trie(pre[i], i);
    }
    for (int i = 1; i <= r; i++) {
        cin >> suf[i];
        reverse(suf[i].begin(), suf[i].end());
        s.build_trie(suf[i], i);
    }
    p.rebuild_trie(); s.rebuild_trie();
    for (int i = 1; i <= l; i++) if (p.vis[i]) p.build_trie(pre[i], i);
    for (int i = 1; i <= r; i++) 
        if (s.vis[i]) reverse(suf[i].begin(), suf[i].end()), s.build_trie(suf[i], i);
    p.build_AC(); s.build_AC();
    cin >> ss;
    p.quep(ss); s.ques(ss);
    for (int i = ss.size() - 1; i >= 0; i--) {
        s.mat[i] += s.mat[i + 1];
        // cout << i << ' '<<s.mat[i] << " kkk\n";
    }
    // cout << '\n';
    ll ans = 0;
    for (int i = 0; i < ss.size(); i++) {
        if (p.mat[i]) {
            // cout << i << ' ' << p.mat[i] << " xxx\n " ;
            ans += s.mat[i];
            ans -= s.que(pre[p.mat[i]]);
        }
    }
    cout << ans << endl;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T; cin >> T;
    while(T--) solve();
}

1010

期望dp,对答案dp取min,转移时只需考虑当前时间是否需要提交

posted @ 2025-08-02 15:59  lyrrr  阅读(19)  评论(0)    收藏  举报