「CF1580D」Subsequence 题解

本文网址:https://www.cnblogs.com/zsc985246/p/17509039.html ,转载请注明出处。

传送门

「CF1580D」Subsequence

题目大意

有一个长度为 \(n\) 的整数序列 \(a\),所有元素都是不同的。

定义一个子序列 \(a_{b_1},a_{b_2},...,a_{b_m}\) 的价值为

\[\sum_{i = 1}^m (m \cdot a_{b_i}) - \sum_{i = 1}^m \sum_{j = 1}^m f(\min(b_i, b_j), \max(b_i, b_j)) \]

其中 \(f(i,j)=min(a_i,a_{i+1},..., a_j)\)

你需要选择一个长度为 \(m\)\(a\) 的子序列,使得其价值最大。

如果一个序列 \(s\) 可以通过删除序列 \(t\) 中几个元素(可以不删除任何元素或删除全部元素)得到,那么这个序列 \(s\) 就是序列 \(t\) 的一个子序列。

\(1 \le m \le n \le 4000,1 \le a_i < 2^{31}\)

思路

价值的定义式非常难看,我们先将它化简。

发现当 \(i=j\) 时,\(f(\min(b_i,b_j),\max(b_i,b_j))=a_{b_i}\)

所以我们直接提出来。

\[= \sum_{i = 1}^m (m-1) \cdot a_{b_i} - \sum_{i = 1}^m \sum_{j = 1,i \neq j}^m f(\min(b_i,b_j),\max(b_i,b_j)) \]

发现后面的式子 \(i=x,j=y\)\(i=y,j=x\) 的答案是相同的,所以只考虑 \(i<j\)

\[= \sum_{i = 1}^m (m-1) \cdot a_{b_i} - \sum_{i = 1}^m \sum_{j = i + 1}^m 2 \times f(b_i,b_j) \]

然后发现,前面的求和每个数都算了 \(m-1\) 次,而后面的求和是我们熟悉的,每个数也被算了 \(m-1\) 次,所以可以把前面的求和拆进后面。

\[= \sum_{i = 1}^m \sum_{j = i + 1}^m a_{b_i} + a_{b_j} - 2 \times f(b_i,b_j) \]

后面的 \(a_{b_i} + a_{b_j} - 2 \times f(b_i,b_j)\) 感觉有点熟悉。我们求树上两点距离不就是 \(dep_x + dep_y - 2 \times dep_{lca(x,y)}\) 吗?

所以我们需要构造一棵树,使得一点 \(x\) 到根节点的距离为 \(a_x\),且两点 \(x,y\)\(lca\) 到根节点的距离为 \(f(x,y)\)

第一个条件很好满足,只需要让 \(x\) 连向 \(fa_x\) 的边权为 \(a_x-a_{fa_x}\) 即可。

对于第二个条件,我们知道 \(f(x,y)\) 在原序列上表示区间 \([x,y]\) 的最小值。这不就是笛卡尔树?

然后我们就只需要在树上找出 \(m\) 个点,使得这些点的两两距离之和最大。

我们直接做树上背包。

因为边的贡献只与两边选择的点的个数有关,所以可以只考虑子树内贡献最大。

定义 \(f_{x,i}\) 表示在 \(x\) 子树中选择 \(i\) 个点,子树外选 \(m-i\) 个点,子树内边权的贡献最大值。

转移只需要枚举 \(son_x\) 子树内的点数和 \(x\) 子树内 \(son_x\) 子树外的点数。复杂度 \(O(n^2)\)

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=4000+10;
using namespace std;

ll n,m;
ll a[N];
ll top,s[N];
ll son[N][2],w[N][2];//0表示左儿子,1表示右儿子
ll siz[N];//大小
ll f[N][N];//f[x][i]表示在x子树中选择i个点,子树内边权的贡献最大值

void dfs(ll x){
	siz[x]=1;
	For(I,0,1){
		ll y=son[x][I];
		if(y){
			dfs(y);
			Rep(i,min(m,siz[x]),0){//x子树内y子树外的点数
				Rep(j,min(m,siz[y]),0){//y子树内的点数
					//子树内j个,子树外m-j个,两两配对就是经过次数
					f[x][i+j]=max(f[x][i+j],f[x][i]+f[y][j]+j*(m-j)*w[x][I]);
				}
			}
			siz[x]+=siz[y];
		}
	}
}

int main(){
	
	scanf("%lld%lld",&n,&m);
	For(i,1,n){
		scanf("%lld",&a[i]);
	}
	//建笛卡尔树
	For(i,1,n){
		ll t=top;
		while(top&&a[s[top]]>a[i])son[s[top-1]][1]=s[top],top--;
		if(top!=t)son[i][0]=s[top+1];
		s[++top]=i;
	}
	while(top)son[s[top-1]][1]=s[top],top--;
	//赋边权
	For(i,1,n){
		if(son[i][0])w[i][0]=a[son[i][0]]-a[i];
		if(son[i][1])w[i][1]=a[son[i][1]]-a[i];
	}
	//树上背包
	dfs(s[1]);
	
	printf("%lld",f[s[1]][m]);
	
	return 0;
}

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!

posted @ 2023-06-27 15:39  zsc985246  阅读(332)  评论(1编辑  收藏  举报