[Codeforces1249F]Maximum Weight Subset

题意

求一棵树的最大点权和子树,且子树里任意两点的距离大于 K K


题解

树上 D P DP (默认 1 1 为树根)

为了方便把将题目要求的大于 K K 改成大于等于 K + 1 K+1 ,即一开始 K = K + 1 K=K+1

算法一

f [ u ] [ k ] f[u][k] 表示以 u u 为根的子树里离 u u 最近的点距离 u u k k 的答案

那么对于 u u 的子节点 v v

f [ u ] [ k ] = { max { f [ v 0 ] [ k 1 ] + v v 0 max j k 1 f [ v ] [ j ] } k 1 a [ u ] k = 0 f[u][k]= \begin{cases} \max\{f[v_0][k-1]+\sum_{v\neq v_0}\max_{j\ge k-1}f[v][j]\}&k\ge1\\ a[u]&k=0 \end{cases}

可以发现这个算法复杂度是 O ( n 4 ) O(n^4) ,而且答案也不好统计

算法二

f [ u ] [ k ] f[u][k] 表示以 u u 为根的子树里离 u u 最近的点与 u u 的距离大于等于 k k 的答案

显然 f [ u ] [ k ] = max j k f [ u ] [ j ] ; f [ u ] [ 0 ] f[u][k]=\max_{j\ge k} f[u][j];f[u][0] 初值为 a [ u ] a[u]

考虑将 u u 的子节点 v v 的答案合并到 u u 的答案上去

f [ u ] [ k ] = max 0 k n { f [ u ] [ k ] + f [ v ] [ max ( k , K k ) 1 ] f [ u ] [ max ( k , K k ) ] + f [ v ] [ k 1 ] f[u][k]=\max_{0\le k\le n}\begin{cases} f[u][k]+f[v][\max(k,K-k)-1]\\ f[u][\max(k,K-k)]+f[v][k-1] \end{cases}

实现时用一个临时数组将答案存下,枚举完后再更新 f [ u ] [ k ] f[u][k] ,最后再求一遍后缀最大值

最后答案即为 f [ 1 ] [ 0 ] f[1][0]

时间复杂度为 O ( n 3 ) O(n^3)

算法三

考虑对算法二进行优化,设 L e n u Len_u 表示 u u 子树里的点离 u u 最远的距离

那么上面 k k 的枚举范围就可以缩小到 0 k L e n v 0\le k\le Len_v ,因为 k k 大了之后 f [ v ] f[v] 的值就不存在了

时间复杂度 O ( O(\sum 长链长 ) = O ( n 2 ) )=O(n^2)

算法四

为了方便将 f [ v ] f[v] 往后整体挪一位,根据定义新的 f [ v ] [ 0 ] = f [ v ] [ 1 ] f[v][0]=f[v][1] ,那么

f [ u ] [ k ] = max { f [ u ] [ k ] + f [ v ] [ max ( k , K k ) ] f [ u ] [ max ( k , K k ) ] + f [ v ] [ k ] f[u][k]=\max\begin{cases} f[u][k]+f[v][\max(k,K-k)]\\ f[u][\max(k,K-k)]+f[v][k] \end{cases}

可以发现这样 f [ u ] f[u] f [ v ] f[v] 就是对偶的了

然后可以注意到, D P DP 只会影响到 0 k min ( L e n u , L e n v ) 0\le k\le \min(Len_u,Len_v) (因为长了就一定会有一个没有值了)

于是就可以考虑长链剖分,每次将短链合并到长链上

时间复杂度 O ( n ) O(n)

void Merge(deque<int>&f,deque<int>&g){//启发式合并
	if(f.size()<g.size())
		swap(f,g);
	vector<int>Tmp=vector<int>(g.begin(),g.end());//复制短链
	for(int k=0;k<(int)g.size();++k){
		int Res=max(k,K-k);
		if(Res<(int)f.size())
			Tmp[k]=max(Tmp[k],f[Res]+g[k]);
		if(Res<(int)g.size())
			Tmp[k]=max(Tmp[k],f[k]+g[Res]);
	}
	int Max=0;
	for(int k=g.size()-1;k>=0;--k){
		Max=max(Max,Tmp[k]);
		f[k]=max(f[k],Max);
	}//更新后缀最大值
}
deque<int> dsu(int u,int p){
	deque<int>Now={a[u]};
	for(auto v:G[u])if(v!=p){
		deque<int>Son=dfs(v,u);
		Son.push_front(Son.front());//整体向后移一位
		Merge(Now,Son);
	}
	return Now;
}

算法五

考虑选了一个点 u u 就加上他的权值 a u a_u ,那么他周围与之距离小于 K K 的点都不能选

考虑把这些点的权值都减去 a u a_u ,相当于造成不选的影响

如果有的点点权变成了负数,那么这个点一定不会选

如果有的点点权还是正数,那就再加上这个点的权值进行 补差 ,就相当于选了这个点而没有选 u u

然后就是选点顺序的问题,一种可行的方案就是从叶子节点开始,因为叶子节点没有儿子了,不会有后效性

时间复杂度 O ( n 2 ) O(n^2)

void dfs(int u,int p,int dis){
    if(dis>=K)return;
    a[u]-=Val;
    for(auto v:G[u])if(v!=p)
    	dfs(v,u,dis+1);
}
inline void Bfs(){
	int h=1,t=1;
    q[1]=vis[1]=1;
    while(h<=t){
        int u=q[h++];
        for(auto v:G[u])
        	if(!vis[v]){
        		vis[v]=1;
        		q[++t]=v;
        	}
    }
}
inline int sol(){
	int Ans=0;
	Bfs();
    for(int i=n;i>=1;--i){
        int u=q[i];Val=a[u];
        if(Val<=0)continue;
        Ans+=Val;
        dfs(u,0,0);
    }
    return Ans;
}

猜你喜欢

转载自blog.csdn.net/BeNoble_/article/details/102722530