“蔚来杯“2022牛客暑期多校训练营10 K.You are given a tree...

原题链接

传送

题目大意

给定包含 n n n 个节点的树 T = ( V , E ) T=(V,E) T=(V,E), 节点 i i i 具有权值 a i a_i ai,每条边有一定边权值。
求子集 S ⊆ V S \sube V SV,满足最多有 k k k 个点具有不同的权值,且最大化“生成边”的权值和,边 e e e 是生成边,当且仅当存在 u , v ∈ S u,v \in S u,vS,边 e e e u , v u,v u,v 的简单路径上。
输出最大的“生成边”的权值和。

题解

考虑点权范围 1 ≤ a i ≤ k 1 \leq a_i \leq k 1aik
我们考虑用状压DP枚举每一位的状态,在树上更新答案。
d p x , v dp_{x,v} dpx,v 表示在 x x x 的子树下,状态为 v v v 内的点权所能组成的最大边权值和。
可以发现, 将子情况汇总,
加入链接顶点的边所能构成的更新可以分成两部分:
该边的连接的子节点和其他的部分,由于点权不冲突,
所以转移式显然为为:
d p x , v = m a x ( d p x , v , d p s o n , s + d p x , v − s + w )     ( s ∈ v ) dp_{x,v}=max(dp_{x,v},dp_{son,s}+dp_{x,v-s}+w)\ \ \ (s\in v) dpx,v=max(dpx,v,dpson,s+dpx,vs+w)   (sv)
枚举即可,复杂度为 O ( n ∗ 3 k ) O(n*3^k) O(n3k)
考虑回点权初始范围,最终选取的点权数肯定在 1 ≤ a i ≤ k 1 \leq a_i \leq k 1aik 内,
随机分配一个其中的权值给 a i a_i ai
如果随机分配后找到一组解,那它在分配前肯定也是一组符合的情况,
而原问题的最优解在某种特殊情况下,即其中的颜色刚好分配不同,肯定能给出结果,之前的状压也肯定能找出该结果。
选取随机数使得最优解中各不相同的概率为 k ! k k k!k^k k!kk ,大约为 1 26 \frac{1}{26} 261,在选取一个合适的数据组数(大约为200左右)进行计算验证后,即可得出最优解。
复杂度为 O ( T ∗ n ∗ 3 k ) O(T*n*3^k) O(Tn3k)

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e3+5;
const ll inf=1e18+7;         //-1e9~1e9
ll dp[N][1<<5],ans;
vector<pair<int,int> > v1[N],v[N];
int f[N],w[N];
int n,k,T,m;
void dfs(int x,int fa)        //状压DP
{
    for(int i=1;i<=m;i++)
        dp[x][i]=-inf;
    dp[x][0]=0;
    int now=1<<w[f[x]];
    for(int i=0;i<v[x].size();i++)           //从子树转移
    {
        pair<int,int> son=v[x][i];
        if(son.first==fa)
            continue;
        dfs(son.first,x);
        for(int p=m;p>=0;p--)
            for(int j=p;j;j=(j-1)&p)       //枚举状态
            {
                ll tp=dp[son.first][j]+dp[x][p^j]+son.second;
                dp[x][p]=max(dp[x][p],tp);
                if(j!=p || (j&now)==0)
                    ans=max(ans,tp);     //比较最优解
            }
    }
    dp[x][now]=max(dp[x][now],0ll);
}
int main()
{
    scanf("%d%d",&n,&k);
    m=(1<<k)-1;
    for(int i=1;i<=n;i++)
        scanf("%d",&f[i]);
    for(int i=2;i<=n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        v[x].push_back(make_pair(y,z));
        v[y].push_back(make_pair(x,z));
    }
    T=200;
    while(T--)
    {
        for(int i=1;i<=n;i++)       //随机
            w[i]=rand()%k;
        dfs(1,0);
    }
    printf("%lld\n",ans);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值