树形DP CCPC威海-C. Rencontre

博客探讨了一道关于树形动态规划的图论题目,要求在树上找到点r,使得从三个集合中选取的点到r的最短路径和最小。利用结论:r位于特定路径的交点,简化问题并进行两两集合的枚举处理,通过计算边对答案的贡献来求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

是我比较弱的图论题,比赛的时候没出.
传送门
题意:给定一棵树和三个集合,要求你从每个集合中任选一点,在得到三个点u,v,w后再在树上找到一个点r,使得dis(u,r)+dis(v,r)+dis(w,r)最小.
有一个结论是:这个点r一定在路径uv,vw,uw三个路径的交点上.
首先,可以确定的是,这三条路径一定会有交点,(因为是一棵树,树上的一个点到另一个点只有一条路径),当然这个交点唯一.
那么dis(u,r)+dis(v,r)+dis(w,r)=(dis(u,v)+dis(v,w)+dis(u,w))/2
有了这个结论后题目就变简单了:两个集合内各选一个点,计算出满足条件的任意点对的距离和.

三个集合两两枚举处理即可.
计算的时候有个技巧:考虑每条边对答案的贡献即可,即边的两边的属于集合a的元素和属于集合b的元素的数量统计一下即可进行运算.

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200500
#define ll long long
vector<pair<int,ll>>e[MAXN];
ll cnt[MAXN];
int x[MAXN][3];
ll sz[MAXN][3];

long double ans=0;
void solve(int rt,int pre)
{
    for(auto it:e[rt]) {
        if (it.first == pre)continue;
        int so=it.first;
        solve(it.first,rt);
        ll now=sz[so][0]*(sz[1][1]-sz[so][1])*cnt[2];
        now+=sz[so][1]*(sz[1][0]-sz[so][0])*cnt[2];

        now+=sz[so][1]*(sz[1][2]-sz[so][2])*cnt[0];
        now+=sz[so][2]*(sz[1][1]-sz[so][1])*cnt[0];

        now+=sz[so][2]*(sz[1][0]-sz[so][0])*cnt[1];
        now+=sz[so][0]*(sz[1][2]-sz[so][2])*cnt[1];
        ans+=now/2.0/(1ll*cnt[0]*cnt[1]*cnt[2])*it.second;
        //注意这里一定要先除再乘,不然会超ll范围
    }
}
void root(int rt,int pre)
{
    for(int i=0;i<3;i++)
    {
        if(x[rt][i])sz[rt][i]++;
    }
    for(auto it:e[rt]){
        if(it.first==pre)continue;
        root(it.first,rt);
        for(int j=0;j<3;j++)
            sz[rt][j]+=sz[it.first][j];
    }
}
int main()
{
    int n;cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        e[u].push_back({v,w});
        e[v].push_back({u,w});
    }
    for(int i=1;i<=3;i++)
    {
        int m;
        scanf("%d",&m);
        cnt[i-1]=m;
        for(int j=1;j<=m;j++)
        {
            int cur;cin>>cur;
            x[cur][i-1]=1;
        }
    }
    root(1,0);
    solve(1,0);
    printf("%.7Lf\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值