2023牛客暑期多校训练营6 A-Tree (kruskal重构树))

文章目录

题目大意


( 0 ≤ a i ≤ 1 ) , ( 1 ≤ c o s t i ≤ 1 0 9 ) (0\leq a_i\leq 1),(1 \leq cost_i\leq 10^9) (0≤ai≤1),(1≤costi≤109)

题解

提供一种新的算法,kruskal重构树。

该算法重新构树,按边权排序每一条边后,

新建一个点为"两边的节点所在最大节点"的父节点,该点点权为该边边权。

该树有一些特征:

①:是一个二叉树。

③:原节点全部为叶节点。

②:两个节点的LCA的点权就是其原最短路径的最大边权。
具体 Kruskal 算法学习

建树可以用并查集计算。

了解了这个算法我们再看问题,要求最大边权,这点可以用kruskal维护。

对于某个不为叶节点的节点 x x x ,它左儿子与右儿子匹配的黑白节点的最大边权显然为 w x w_x wx 。

显然的,我们可以枚举左右儿子节点中的黑白节点个数,乘上点权,即为该点的贡献。

我们发现答案可以通过 d f s dfs dfs 顺序从下往上来求解,且不会造成前效性,所以树形DP可以很好的解决这道题。

设 d p x , b dp_{x,b} dpx,b 表示在 x x x 的子树内有 b b b 个黑色节点的最优解。
d p x , b = m a x ( d p s o n , b l a c k 1 + d p s o n , b 2 + w x ∗ ( b l a c k 1 ∗ w h i t e 2 + b l a c k 2 ∗ w h i t e 1 ) ) dp_{x,b}=max(dp_{son,black1}+dp_{son,b2}+w_x*(black1*white2+black2*white1)) dpx,b=max(dpson,black1+dpson,b2+wx∗(black1∗white2+black2∗white1))
white/black_1/2表示1/2的子树中有几个白色/黑色节点
且black1+black2=b

我们发现枚举 b b b 的黑白分布情况,最多需要合并 m i n ( s u m s o n l , s u m s o n r ) min(sum_{sonl},sum_{sonr}) min(sumsonl,sumsonr)次,

不然的话就需要从大的部分取一部分给数量少的一颗子树。

特殊的,对于叶节点
d p x , b = ( w x = = b ˆ 1 ) ∗ − c o s t x dp_{x,b}=(w_x==b \^\ 1)*-cost_x dpx,b=(wx==b ˆ1)∗−costx

剩下的就好处理多了,写个DFS遍历一下即可处理。

计算时间复杂度,对于kruskal重构树,合并时长度最大为 l o g n log_n logn

即时间复杂度为 O ( N 2 l o g N ) O(N^2log_N) O(N2logN) 可以通过。

参考代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=6e3+5;
const int inf=1e18+7;
struct node{
    int x,y,w;
}f[N];
int fa[N],cost[N];
int w[N];
int n,m,t,ans;
int sonl[N],sonr[N];
int sum[N];
int dp[N][3000];
void dfs(int x)
{
    if(x<=n)                   //叶节点
    {
        dp[x][0]=(w[x]==1)*(-cost[x]);
        dp[x][1]=(w[x]==0)*(-cost[x]);
//        cout<<x<<" "<<0<<" "<<dp[x][0]<<endl;
//        cout<<x<<" "<<1<<" "<<dp[x][1]<<endl;
        sum[x]=1;
    }
    else
    {
        dfs(sonl[x]);
        dfs(sonr[x]);
        int res=min(sum[sonl[x]],sum[sonr[x]]);           //启发式合并平均复杂度为log_n
        sum[x]=sum[sonl[x]]+sum[sonr[x]];
        for(int i=0;i<=sum[sonl[x]];i++)
            for(int j=0;j<=sum[sonr[x]];j++)
                dp[x][i+j]=-inf;
        for(int i=0;i<=sum[sonl[x]];i++)           //枚举黑色节点个数
        {
            for(int j=0;j<=sum[sonr[x]];j++)              //DP转移
            {
                int s=dp[sonl[x]][i]+dp[sonr[x]][j]+w[x]*(i*(sum[sonr[x]]-j)+(sum[sonl[x]]-i)*j);
                dp[x][i+j]=max(dp[x][i+j],s);
                ans=max(ans,dp[x][i+j]);
            }
        }            
    }
}
int cmp(node a,node b)
{
    return a.w<b.w;
}
int sf(int x)
{
    if(fa[x]==x)
        return x;
    return fa[x]=sf(fa[x]);
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%lld",&w[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&cost[i]);
    for(int i=1;i<n;i++)
        scanf("%lld%lld%lld",&f[i].x,&f[i].y,&f[i].w);
    sort(f+1,f+n,cmp);
    t=n;
    for(int i=1;i<=2*n;i++)
        fa[i]=i;
    for(int i=1;i<n;i++)             //kruskal构树
    {
        int x=sf(f[i].x),y=sf(f[i].y);
        fa[x]=++t;
        fa[y]=t;
        w[t]=f[i].w;
        sonl[t]=x;
        sonr[t]=y;
    }
    dfs(t);
    printf("%lld",ans);
}
相关推荐
A_Tai2333333几秒前
贪心算法解决用最少数量的箭引爆气球问题
算法·贪心算法
唐叔在学习12 分钟前
【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现
java·算法·排序算法
_nirvana_w_12 分钟前
C语言实现常用排序算法
c语言·算法·排序算法
唐叔在学习20 分钟前
【唐叔学算法】第18天:解密选择排序的双重魅力-直接选择排序与堆排序的Java实现及性能剖析
数据结构·算法·排序算法
Kenneth風车1 小时前
【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)11
算法·机器学习·分类
最后一个bug1 小时前
rt-linux中使用mlockall与free的差异
linux·c语言·arm开发·单片机·嵌入式硬件·算法
蹉跎x2 小时前
力扣1358. 包含所有三种字符的子字符串数目
数据结构·算法·leetcode·职场和发展
rainoway2 小时前
CRDT宝典 - yata算法
前端·分布式·算法
巫师不要去魔法部乱说3 小时前
PyCharm专项训练4 最小生成树算法
算法·pycharm
IT猿手3 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解GLSMOP1-GLSMOP9及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·算法·机器学习·matlab·强化学习