CF1580D Subsequence 题解
第一眼看到这个题目的时候,我们要关注这个式子的特点,并尝试用人话把他翻译一下
所以,原式
首先看到后面那两个
则原式
注意到在后面的
则原式化为
如果做惯了数论题,或许你猛然就会有一个直觉,这跟树上两点间的距离实在太像了
树上两点间的距离为
这里为
我们思考怎么转换
所以我们要构造一棵带权的树,他满足任意两点间的
则
于是,这题就转换为在树上找
树上背包即可
#include <bits/stdc++.h>
using namespace std;
const int N=4e3+5;
int ls[N],rs[N],stk[N],top=0,lw[N],rw[N],a[N],n,m;
int sze[N];
typedef long long ll;
ll f[N][N];
inline void dfs(int x)
{
sze[x] = 1;
if(ls[x])
{
dfs(ls[x]);
for(int i=min(m,sze[x]);i>=0;i--)
for(int j=min(m,sze[ls[x]]);j>=0;j--)
f[x][i+j] = max(f[x][i+j],f[x][i] + f[ls[x]][j] + 1ll*j*(m-j)*lw[x]);
sze[x] += sze[ls[x]];
}
if(rs[x])
{
dfs(rs[x]);
for(int i=min(m,sze[x]);i>=0;i--)
for(int j=min(m,sze[rs[x]]);j>=0;j--)
f[x][i+j] = max(f[x][i+j],f[x][i] + f[rs[x]][j] + 1ll*j*(m-j)*rw[x]);
sze[x] += sze[rs[x]];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
int tmp = top;
while(top && a[stk[top]]>a[i]) rs[stk[top-1]]=stk[top],top--;
if(top<tmp) ls[i]=stk[top+1];
stk[++top] = i;
}
while(top) rs[stk[top-1]]=stk[top],top--;
for(int i=1;i<=n;i++)
{
// printf("%d,%d\n",ls[i],rs[i]);
if(ls[i]) lw[i] = abs(a[i]-a[ls[i]]);
if(rs[i]) rw[i] = abs(a[i]-a[rs[i]]);
}
int minn=0x7f7f7f7f,pos=0;
for(int i=1;i<=n;i++)
if(a[i]<minn)minn=a[i],pos=i;
assert(stk[1] == pos);
dfs(pos);
printf("%lld\n",f[pos][m]);
return 0;
}
你或许会奇怪于这个写法的时间复杂度为何是
观察树上背包时,我们其实是把两个集合
接下来又是一个奇妙的转化
我们建立一张
每当我们要合并点集
显然,因为
所以每一条边就代表着
而
所以时间复杂度为
你可以认为这是一种势能分析的思想
完结撒花 !!!