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

原题题面:https://ac.nowcoder.com/acm/contest/33195/K

题目大意

给定包含 n ( 1 ≤ n ≤ 5000 ) n(1\le n\le 5000) n(1n5000)个点的树 T = ( V , E ) T=(V,E) T=(V,E),节点 i i i具有权值 a i ( 1 ≤ a i ≤ n ) a_i(1\le a_i\le n) ai(1ain),每条边具有一定边权值 w i ( − 1 0 9 ≤ w i ≤ 1 0 9 ) w_i(-10^9\le w_i\le10^9) wi(109wi109)

求子集 S ⊆ V S\subseteq V SV,满足最多有 k ( 2 ≤ k ≤ 5 ) k(2\le k\le5) k(2k5)个点具有不同的权值,且最大化“生成边”的权值和。边 e e e是生成边,当且仅当存在 u , v ∈ S u,v\in S u,vS,边 e e e u , v u,v u,v的简单路径上。

输出最大的“生成边”的权值和。

解题思路

考虑若点权值种数较少,可以采用树状数组和状压 d p dp dp,设 d p x , V dp_{x,V} dpx,V表示 x x x节点的子树中,已选点权种类二进制为 V V V的最大边权值,枚举儿子 x x x,枚举 x x x的方案 S S S,则其他儿子的方案为 V − S V-S VS,进行 d p dp dp转移即可,设点全集为 A A A

由此可得转移式:
d p x , V = m a x ( d p x , V , d p x , V − S + d p v , S + w s ) ( V ⊆ A , S ⊆ V , s ∈ s o n x ) dp_{x,V}=max(dp_{x,V},dp_{x,V-S}+dp_{v,S}+w_s)(V\subseteq A,S\subseteq V,s\in son_x) dpx,V=max(dpx,V,dpx,VS+dpv,S+ws)(VA,SV,ssonx)

但本题权值最多有 n n n种,需枚举 x , v , S , V x,v,S,V x,v,S,V,总复杂度为 O ( N 3 n ) O(N3^n) O(N3n),显然不行,可以用随机化优化,将每个点的权值处理为 [ 1 , k ] [1,k] [1,k],从而降低复杂度,处理200次,可将正确率升至 99.96 % 99.96\% 99.96%,时间复杂度 O ( T N 3 k ) O(TN3^k) O(TN3k),最多约大于 2 × 1 0 8 2\times10^8 2×108,勉强可过。

代码实现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
template <class T> inline void read(T&x){
	char c,f=' ';
	while(!isdigit(c=getchar()))f=c;
	x=c^48;
	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
	if(f=='-')x=-x;
}
struct node{
	int v;
	ll w;
	node(int v,int w):v(v),w(w){}
};
const int N=5e3+5,T=300;
const ll inf=0x3f3f3f3f3f3f3f;
vector<node>ve[N];
int n,k,a[N],b[N],c[N];
ll ans,f[N][1<<5],t;
void dfs(int u,int fa){
	for (int S=0;S<1<<k;++S)f[u][S]=-inf;
    f[u][0]=0;
    f[u][1<<b[u]]=0;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i].v;
		if(v==fa)continue;
		dfs(v,u);
        for(int V=0;V<1<<k;V++)f[v][V]+=ve[u][i].w;
		for(int V=(1<<k)-1;V;V--){
			for(int A=V;A;A=(A-1)&V){
				f[u][V]=max(f[u][V],f[u][V-A]+f[v][A]);
				if(V-A)ans=max(ans,1ll*f[u][V-A]+f[v][A]);
			}
		}
	}
}
int main(){
	read(n),read(k);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<n;i++){
		int u,v;ll w;
		read(u),read(v),read(w);
		ve[u].push_back(node(v,w));
		ve[v].push_back(node(u,w));
	}
	for(t=1;t<=T;t++){
		for(int i=1;i<=n;i++)c[i]=rand()%k;
        for(int i=1;i<=n;i++)b[i]=c[a[i]];
		dfs(1,0);
	}
	cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值