9.21 注册题

题意

给定一颗点带权无根树,请你选定一个根并对这棵树进行深度优先遍历,得到一个点的经过顺序(即\(dfs\)序):\(v_1,v_2...v_n\),记点\(u\)的点权为\(A_u\)

请最小化下面式子的值
\[ \sum_{i=1}^n i\times A_{v_i} \]

解法

大佬们都说这题是煞笔题。。我还是太菜了

设当前点为\(u\),我们已经求得了\(u\)所有儿子的最优答案

现在我们要对\(u\)的所有子树排序(即确定遍历顺序),使得\(u\)的答案最优

对于这类安排顺序求最优解的问题,我们可以想到邻项交换排序

考虑\(u\)的两个儿子\(v,w\),它们是两个相邻的子树,我们现在考虑交换它们的遍历顺序能否使答案更优

首先我们把当前问题看做是一个重叠子问题,即\(u\)即是当前的根(如果当前根非\(u\),我们可以用\(u\)的答案加上\(sum_u \times (size_{pre_u} +1)\)得到新答案,这就成为一个重复问题了)

设之前子树的答案为\(ans_{pre}\),这显然是一个定值(在当前情况下)

\(u\)\(v\)之前
\[ ans=A[u]+ans_{pre}+(ans_u+sum_u\times (size_{pre}+1))+(ans_v + sum_v\times (size_{pre}+size_u+1)) \]
虽然这个式子看上去很复杂,但实际上有很多定值

我们把它进行化简
\[ ans=A[u]+ans_{pre}+ans_u+ans_v+sum_u+sum_v+(sum_u+sum_v)\times size_{pre}+sum_v\times size_u \]
这样如果我们把\(u\)\(v\)进行交换,影响答案的改变只有\(sum_v\times size_u\)

那么我们排序的依据就成了最小化\(sum_v\times size_u\)

依照这个排序方法,我们有了一个\(O(N^2\log N)\)的枚举根的算法

但是我们发现如果枚举根重新\(DP\)出所有信息会有许多重复信息没有得到利用

此时我们可以采用换根\(DP\)的方式:每次换根后将其原来父亲当做其儿子进行处理

具体的排序可以用\(multiset\)来实现:由于每个点的答案只由其儿子(也就是与其直接相连的点)确定,我们对每个点维护一个按照前文提到的规则排序的\(multiset\),其中的元素是以该点为根时其子节点的答案

根据这个\(multiset\)我们可以迅速算出以该节点为根时的答案

代码

#include <cstdio>
#include <set>

using namespace std;

const int N = 2e5 + 10;

#define int long long 

void dfs_1(int);
void dfs_2(int);
void dfs_3(int);

struct node {
    int id, sz, su; 
    bool operator < (const node& rhs) const { return sz * rhs.su < rhs.sz * su; }
};

multiset<node> s[N];
typedef multiset<node>::iterator iter;

int n;
int val[N];

int cap;
int head[N], to[N << 1], nxt[N << 1];

int fa[N], sum[N], siz[N];

long long ans;
long long f[N];

inline void add(int x, int y) { to[++cap] = y, nxt[cap] = head[x], head[x] = cap; }

signed main() {
    
    scanf("%lld", &n);
    
    int u, v;
    for (int i = 1; i < n; ++i) {
        scanf("%lld%lld", &u, &v);
        add(u, v), add(v, u);
    }
    
    for (int i = 1; i <= n; ++i)  scanf("%lld", val + i);
    
    dfs_1(1);
    dfs_2(1);
    
    ans = f[1];
    dfs_3(1);
    
    printf("%lld\n", ans);
    
    return 0;
}

void dfs_1(int x) {
    siz[x] = 1, sum[x] = val[x];
    for (int i = head[x]; i; i = nxt[i]) 
        if (to[i] != fa[x])  fa[to[i]] = x, dfs_1(to[i]), sum[x] += sum[to[i]], siz[x] += siz[to[i]];
}

void dfs_2(int x) {
    f[x] = val[x];
    for (int i = head[x]; i; i = nxt[i])
        if (to[i] != fa[x])  dfs_2(to[i]), s[x].insert((node){to[i], siz[to[i]], sum[to[i]]});
    
    int presz = 1;
    for (iter it = s[x].begin(); it != s[x].end(); ++it) 
        f[x] += f[it -> id] + presz * sum[it -> id], presz += it -> sz;
}

void dfs_3(int x) {
    
    int presz = 1, presum = val[x];
        
    if (x != 1) {
        f[x] = val[x];  
        s[x].insert((node){fa[x], n - siz[x], sum[1] - sum[x]});
        for (iter it = s[x].begin(); it != s[x].end(); ++it)
            f[x] += f[it -> id] + presz * it -> su, presz += it -> sz;
    }
    
    ans = min(ans, f[x]), presz = 1;
    
    long long tmp = f[x];
    for (iter it = s[x].begin(); it != s[x].end(); ++it) {
        presum += it -> su;
        if (it -> id != fa[x]) {
            f[x] = tmp - f[it -> id] - presz * sum[it -> id] - (sum[1] - presum) * siz[it -> id];
            dfs_3(it -> id);
        }
        presz += it -> sz;
    }
}

转载于:https://www.cnblogs.com/VeniVidiVici/p/11569699.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值