「CF1580D」Subsequence 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17509039.html ,转载请注明出处。
传送门
题目大意
有一个长度为 的整数序列 ,所有元素都是不同的。
定义一个子序列 的价值为
其中 。
你需要选择一个长度为 的 的子序列,使得其价值最大。
如果一个序列 可以通过删除序列 中几个元素(可以不删除任何元素或删除全部元素)得到,那么这个序列 就是序列 的一个子序列。
。
思路
价值的定义式非常难看,我们先将它化简。
发现当 时,,
所以我们直接提出来。
发现后面的式子 和 的答案是相同的,所以只考虑 。
然后发现,前面的求和每个数都算了 次,而后面的求和是我们熟悉的,每个数也被算了 次,所以可以把前面的求和拆进后面。
后面的 感觉有点熟悉。我们求树上两点距离不就是 吗?
所以我们需要构造一棵树,使得一点 到根节点的距离为 ,且两点 的 到根节点的距离为 。
第一个条件很好满足,只需要让 连向 的边权为 即可。
对于第二个条件,我们知道 在原序列上表示区间 的最小值。这不就是笛卡尔树?
然后我们就只需要在树上找出 个点,使得这些点的两两距离之和最大。
我们直接做树上背包。
因为边的贡献只与两边选择的点的个数有关,所以可以只考虑子树内贡献最大。
定义 表示在 子树中选择 个点,子树外选 个点,子树内边权的贡献最大值。
转移只需要枚举 子树内的点数和 子树内 子树外的点数。复杂度 。
代码实现
#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;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现