2020牛客暑期多校训练营(第一场) B Infinite Tree

思路:
1、首先假设这棵树的结果已经确定,我们统计每颗子树的 w[i] 的和 val,显然 val[1] = \(\sum_{i=1}^n w[i]\) , 假设我们当前选定点 u ,处于点 u 时的结果为 f[u], 点 u 的深度为 depeth[u],
接下来遍历 u 的子节点 v,由于已知 f[u], 所以 f[v] = f[u] + (val[1] - 2 * val[v]) * (depeth[v] - depeth[u])。若 f[v] < f[u], 则向 v 转移, 直到不能再转移就是正确结果了。

2、但我们不能直接这么做,因为 m = 1e5, 我们存不下来这么节点, 但是可以发现,有用的点只有 1e5 个给定点和他们的公共祖先 LCA, 所以用虚树来减少节点的数量。
要建立虚树我们需要知道每个关键点的 dfs 序 dfn[i],depeth[i], 和 dfs序相邻两点的lca, 但在这题中,点 dfn[i * i] 一定小于 dfn[(i + 1)* (i + 1)]
原因:假设一个数唯一分解后的结果为 \(x_1^{p_1}x_2^{p_2}x_3^{p_3}x_n^{p_n}...\),那么该节点的深度为 \((\sum_{i = 1}^n p_i) + 1\), 根节点 1 的深度为 1。
接着看 x! 和 (x + 1)! LCA 的深度, 若 (x + 1) 的最大素因子大于 x 的最大素因子,则 x! 和 (x + 1)! 的 LCA 为 1, 否则,
假设 x! 唯一分解后的素因子集合为 {5, 5, 5, 3, 3, 2}, (x + 1)! 唯一分解后的素因子集合为{5, 5, 5, 3, 3, 2} + {3, 3, 2},
根据这颗树的性质,我们很容易得出 x! 和 (x + 1)! LCA 的深度为 6 (3个5, 两个3, 再加上一个根节点), 可以手画一颗树来,很容易发现这个性质。
3、然后建立虚树再dp就好了。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int INF = 1e9 + 7;
typedef long long LL;
int mindiv[maxn], vis[maxn];
int n;
LL w[maxn];
void init(){ // 求mindiv[i]
    for(int i = 2; i < maxn; i++){
        mindiv[i] = INF;
    }
    for(int i = 2; i < maxn; i++){
        if(vis[i]) continue;
        for(int j = 1; 1LL * j * i < maxn; j++){
            mindiv[1LL * i * j] = min(mindiv[i * j], i);
            vis[1LL * i * j] = 1;
        }
    }
}

int tree[maxn];
int lowbit(int x){ // 通过树状数组来对因子的个数计数
    return x & (-x);
}
void Update(int i, int k){
    while(i <= n){
        tree[i] += k;
        i += lowbit(i);
    }
}
int getSum(int i){
    int res = 0;
    while(i > 0){
        res += tree[i];
        i -= lowbit(i);
    }
    return res;
}

struct Edge
{
    int to, next;
} edge[maxn * 4];

int k, head[maxn];

void add(int a, int b){
    edge[k].to = b;
    edge[k].next = head[a];
    head[a] = k++;
}
int top, stk[maxn], depeth[maxn], lcadepth[maxn], tot;
void BuildTree(){ //建立虚树
    top = 1;
    tot = n;
    stk[1] = depeth[1] = 1;
    for(int i = 2; i <= n; i++){
        int cnt = 0;
        int j;
        for(j = i; j != mindiv[j]; j /= mindiv[j]) cnt++; // 计算点 i 深度
        depeth[i] = depeth[i - 1] + cnt + 1;
        lcadepth[i] = getSum(n) - getSum(j - 1) + 1;
        for(j = i; j != 1; j /= mindiv[j]) Update(mindiv[j], 1);
    }

    for(int i = 2; i <= n; i++){
        if(top == 1 || lcadepth[i] == depeth[stk[top]]){
            stk[++top] = i;
            continue;
        }
        while(top > 1 && lcadepth[i] <= depeth[stk[top - 1]]){
            add(stk[top - 1], stk[top]);
            top--;
        }
        if(lcadepth[i] != stk[top]){
            depeth[++tot] = lcadepth[i]; // 要给定非 1 - n 的lca的编号
            head[tot] = -1;
            w[tot] = 0;
            add(tot, stk[top]);
            stk[top] = tot;
        }
        stk[++top] = i;
    }

    while(top > 1){
        add(stk[top - 1], stk[top]);
        top--;
    }
}
LL val[maxn];
void dfs(int u){ // 计算所有节点的val[u]
    val[u] = 0;
    val[u] = w[u];
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        dfs(to);
        val[u] += val[to];
    }
}
LL ans = 0;
void dp(int u, LL res){
    ans = min(ans, res);
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        LL res2 = res + 1LL * (val[1] - 1LL * 2 * val[to]) * (depeth[to] - depeth[u]);
        if(res2 < res){
            dp(to, res2);
        }
    }
}
int main(int argc, char const *argv[])
{
    init();
    while(~scanf("%d", &n)){
        for(int i = 0; i <= n; i++){
            head[i] = -1;
        }
        ans = 0;
        k = 0;
        for(int i = 1; i <= tot * 2; i++) {
            tree[i] = depeth[i] = lcadepth[i] = 0;
        }
        BuildTree();
        for(int i = 1; i <= n; i++){
            scanf("%lld", &w[i]);
        }
        ans = 0;
        dfs(1);
        for(int i = 1; i <= n; i++) {
            ans += 1LL * (depeth[i] - 1) * w[i];
        }
        dp(1, ans);
        printf("%lld\n", ans);
    }
    return 0;
}
posted @ 2020-07-15 17:22  从小学  阅读(245)  评论(0编辑  收藏  举报