Codeforces #665 D. Maximum Distributed Tree(贪心+思维)

在这里插入图片描述
在这里插入图片描述
题目大意:
给定一颗树,现在你需要在 n − 1 n-1 n1条边上加权,使得 ∑ i = 1 i = n − 1 ∑ j = i + 1 j = n f ( i , j ) \sum_{i=1}^{i=n-1}\sum_{j=i + 1}^{j=n}f(i,j) i=1i=n1j=i+1j=nf(i,j)最大。
f ( i , j ) : f(i,j): f(i,j):表示顶点 i i i到顶点 j j j一条简单路径的边权之和。
有三个限制:
1. 1. 1.所有边权的乘积为 k k k
2. 2. 2.边权 > 1 >1 >1
3. 3. 3.边权为 1 1 1的边尽可能的少。
思路:
计算每条边的贡献(即要经过的次数),然后贪心的把大的权分配给贡献多的边。
两个问题:
1. 1. 1.每条的经过的次数怎么在一次遍历中计算完。
2. 2. 2.当质因数大于边的数量与小于等于边的数量时,边权该如何分配。
第一个问题,计算 u u u v v v这条边经过的次数,我们可以认为 u , v u,v u,v是两个不同的集合,现在集合 u u u到集合 v v v通过这条边连接,那么这条边经过的次数就是集合 u u u的个数 ∗ * 集合 v v v的个数。换言之 ( u , v ) = s o n [ v ] ∗ ( n − s o n [ v ] ) (u,v)=son[v]*(n-son[v]) (u,v)=son[v](nson[v])
第二个问题,当质因数的个数小于边的个数,后面的用 1 1 1补充即可,当质因数个数大于边的个数,为了使得答案更大,先把前面 m − ( n − 1 ) + 1 m-(n-1)+1 m(n1)+1的大的质因数合并成一个,然后贪心分配即可。
这里其实有一个问题,当质因数个数大于边的个数,一种最优的应该是把所有质因数合并一个 k ∗ k* k经过次数最多的边,然后其他边权全部为 1 1 1,不过这样不满足第三个限制,所以我们可以直接从大到小贪心即可。
代码:

#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;

const int maxn = 2e5 + 10;
typedef long long int ll;
ll mod = 1e9 + 7;
vector<int>G[maxn];
ll e[maxn];
ll p[maxn];
ll son[maxn];
int cnt;
int n,m;
void dfs(int u,int fu){
    son[u] = 1;
    for(int i = 0; i < G[u].size(); i++){
        int v = G[u][i];
        if(v != fu){
            dfs(v,u);
            son[u] += son[v];
            e[++cnt] = son[v] * (n - son[v]);
        }
    }
}
void solved(){
    cnt = 0;
    cin>>n;
    for(int i = 1; i <= n; i++)G[i].clear();
    for(int i = 1; i < n; i++){
        int u,v;cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    cin>>m;
    for(int i = 1; i <= m; i++)cin>>p[i];
    dfs(1,-1);
    sort(p + 1,p + 1 + m,greater<ll>());
    sort(e + 1,e + 1 + cnt,greater<ll>());
    if(m <= n - 1){
        ll ans = 0;
        for(int i = 1; i <= m; i++)
            ans = ans + p[i] * e[i] % mod;
        for(int i = m + 1; i <= n - 1; i++)
            ans += e[i] % mod;
        cout<<ans % mod<<endl;
    }else{
        ll ans = 1;
        ll x = 1;
        for(int i = 1; i <= m - (n - 1) + 1; i++){
            x = x * p[i] % mod;
        }
        ans = x * e[1] % mod;
        for(int i = 2,j = m - (n - 1) + 2; i <= n - 1; i++,j++){
            ans = ans + p[j] * e[i] % mod;
        }
        cout<<ans % mod<<endl;
    }
}
int main(){
    int t;cin>>t;
    while(t--)
        solved();
    return 0;
}
/*
16
5 10
16 1
14 1
7 5
13 2
16 11
1 7
1 4
3 14
8 16
1 6
4 9
4 12
5 13
1 15
18
45893 9901 51229 15511 46559 28433 4231 30241 29837 34421 12953 6577 12143 52711 40493 89 34819 28571
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值