第七届传智杯程序设计赛道省赛第二场B组

114 阅读4分钟

小苯点兵点将

image.png

image.png

小苯的好数

image.png

完全平方数的性质 : 完全平方数等价于约数个数一定是奇数个。

对于偶数一定合法 ;对于奇数,根据上述性质,不是完全平方数的一定不满足,是完全平方数的一定满足。

证明 : 奇数的因子一定是 奇数 × 奇数, 因子是成对出现的, 所以只有完全平方数有奇数个因子。

image.png

小苯的 ovoovo

image.png

dp[i][j] 表示考虑前 i 个字母, 使用 j 次修改机会, 最多得到多少个不相交的 ovo 子串。

显然考虑最后三个字符修改为 ovo 的使用修改次数即可。

dp[i][j] = max(dp[i][j], dp[i - 3][j - cnt] + 1)

注意 f[i][j] 要继承 f[i - 1][j] 的最大值。

image.png

小苯的水瓶

image.png

属于比较显然的二分。

image.png

小苯的旅行计划

树上差分板子。

我用的树链剖分。

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

constexpr int N = 1E5 + 10;
vector<int> e[N];
// vector<pair<int,int>>ne[N];
#define lc u << 1
#define rc u << 1 | 1
int n, m, r;

int sz[N], son[N], dep[N], fa[N];
int w[N], nw[N], id[N], top[N], tim = 0;

void dfs1(int u, int father){
    sz[u] = 1, dep[u] = dep[father] + 1, fa[u] = father;
    for(int v : e[u]){
        if(v == father) continue ;
        dfs1(v, u);
        sz[u] += sz[v];
        if(sz[v] > sz[son[u]]) son[u] = v;
    }
}
// id : 存 u 剖分之后的编号
// nw : 新编号在树中对应的权值
void dfs2(int u, int tp){
    top[u] = tp, id[u] = ++ tim, nw[tim] = w[u];
    if(!son[u]) return ;
    dfs2(son[u], tp);
    for(int v : e[u]){
        if(v == fa[u] || v == son[u]) continue ;
        dfs2(v, v);
    }
}

struct tree{
    int l, r;
    int sum, add;
}tr[N * 4];

void pushup(int u){
    tr[u].sum = tr[lc].sum + tr[rc].sum;
}

void pushdown(int u){
    if(tr[u].add){
        tr[lc].sum += tr[u].add * (tr[lc].r - tr[lc].l + 1);
        tr[rc].sum += tr[u].add * (tr[rc].r - tr[rc].l + 1);
        tr[lc].add += tr[u].add;
        tr[rc].add += tr[u].add;
        tr[u].add = 0;
    }
}

void build(int u, int l, int r){
    tr[u] = {l, r, nw[l], 0};
    if(l == r) return ;
    int mid = l + r >> 1;
    build(lc, l, mid);
    build(rc, mid + 1, r);
    pushup(u);
}

void update(int u, int l, int r, int k){
    if(l <= tr[u].l && r >= tr[u].r){
        tr[u].sum += k * (tr[u].r - tr[u].l + 1);
        tr[u].add += k;
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if(l <= mid) update(lc, l, r, k);
    if(r > mid) update(rc, l, r, k);
    pushup(u);
}

// 将树从 u 到 v 节点最短路径上的所有节点的值加上 k
void update_path(int u, int v, int k){
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        update(1, id[top[u]], id[u], k);
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u, v);
    update(1, id[v], id[u], k);
}

// 以 u 为根的子树的所有节点的值加 k
// 以 u 为根的子树的 dfs 序是连续的
void update_tree(int u, int k){
    update(1, id[u], id[u] + sz[u] - 1, k);
}

int ask(int u, int l, int r){
    if(l <= tr[u].l && r >= tr[u].r){
        return tr[u].sum;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    int res = 0;
    if(l <= mid) res += ask(lc, l, r);
    if(r > mid) res += ask(rc, l, r);
    pushup(u);
    return res;
}

// 查询树从 u 到 v 节点最短路径上的所有节点的值之和
int ask_path(int u, int v){
    int res = 0;
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        res += ask(1, id[top[u]], id[u]);
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u, v);
    res += ask(1, id[v], id[u]);
    return res;
}

// 查询以 u 为根的子树的节点值之和
int ask_tree(int u){
    return ask(1, id[u], id[u] + sz[u] - 1);
}

int lca(int u, int v){
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}

void init(){
    cin >> n >> m;
    r = 1;
    // n 个结点的权值
    for(int i = 1; i <= n; i ++){
        cin >> w[i];
    }
    // 读入树
    for(int i = 1; i < n; i ++){
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    // 从根节点 dfs
    dfs1(r, 0);
    dfs2(r, r);
    build(1, 1, n); // 用链建线段树
}
// int sz[N], son[N], dep[N], fa[N];
// int w[N], nw[N], id[N], top[N], tim = 0;

// int ddd[N];
// void dfs(int u,int ff) {
//     for(auto [v,w] : ne[u]) {
//         if(v==ff)continue;
//         dfs(v,u);
//         ddd[v] = ddd[u] + 1;
//     }
// }
void solve(){
    cin >> n >> m;
    r = 1;
    for(int i = 1; i <= n; i ++){
        e[i].clear();
        sz[i] = son[i] = dep[i] = fa[i] = w[i] = nw[i] = id[i] = top[i] = tim = 0;// ddd[i]=0;
    }
    vector<tuple<int,int,int>>tmp;
    for(int i = 1; i < n; i ++){
        int u, v, w;
        cin >> u >> v >> w;
        // ne[u].push_back({v,w});
        // ne[v].push_back({u,w});
        tmp.push_back({u,v,w});
    }
    // dfs(1,0);
    // for(auto[u,v,ww]:tmp){
    //     e[u].push_back(v);
    //     e[v].push_back(u);
    //     if(ddd[u] > ddd[v]) {
    //         w[u] = ww;
    //     }
    //     else{
    //         w[v] = ww;
    //     }
    // }
    for(int i = 1; i <= n; i ++){
        // cout<<w[i]<<'\n';
        w[i]=0;
    }


    // 从根节点 dfs
    dfs1(r, 0);
    dfs2(r, r);
    build(1, 1, n); // 用链建线段树
    for(int i = 1; i <= m; i ++){
        int a,b;
        cin >> a >> b;
        update_path(a, b, 1);
        int l = lca(a,b);
        update_path(l,l,-1);
    }
    int res = 0;
    int mx = 0;
    for(auto[u,v,ww]:tmp){
        int peo = ask_path(u,v);
        int l = lca(u, v);
        peo -= ask_path(l,l);
        res += ww*peo;
        mx = max(mx, ww*peo);
        // cout << u << ' ' << v << ' ' << peo << '\n';
        // e[u].push_back(v);
        // e[v].push_back(u);
        // if(ddd[u] > ddd[v]) {
        //     w[u] = ww;
        // }
        // else{
        //     w[v] = ww;
        // }
    }
    cout<<res-mx;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0); 
    int t;cin>>t;while(t--)solve();
    return 0;
}

// 将每个边权映射为点权,具体映射规则为 : 选择一条边两端点中,**较深的那一个** 。

// 此时如果要查询 $u$ 到 $v$ 最短路径的权值之和,会多查询 $lca(u,v)$ 的点权,删去即可 ;

// 修改的话,不修改 $lca(u,v)$ 即可。