「CF1580D」Subsequence 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17509039.html ,转载请注明出处。
传送门
题目大意
有一个长度为 \(n\) 的整数序列 \(a\),所有元素都是不同的。
定义一个子序列 \(a_{b_1},a_{b_2},...,a_{b_m}\) 的价值为
其中 \(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}\),
所以我们直接提出来。
发现后面的式子 \(i=x,j=y\) 和 \(i=y,j=x\) 的答案是相同的,所以只考虑 \(i<j\)。
然后发现,前面的求和每个数都算了 \(m-1\) 次,而后面的求和是我们熟悉的,每个数也被算了 \(m-1\) 次,所以可以把前面的求和拆进后面。
后面的 \(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;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!