P3648 [APIO2014] 序列分割
[APIO2014] 序列分割
题目描述
你正在玩一个关于长度为
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
输入格式
第一行包含两个整数
第二行包含
输出格式
第一行输出你能获得的最大总得分。
第二行输出
如果有多种方案使得总得分最大,输出任意一种方案即可。
样例 #1
样例输入 #1
7 3
4 1 3 4 0 2 3
样例输出 #1
108
1 3 5
提示
你可以通过下面这些操作获得
初始时你有一块
你现在有两块
你现在有三块
所以,经过这些操作后你可以获得四块
限制与约定
第一个子任务共 11 分,满足
第二个子任务共 11 分,满足
第三个子任务共 11 分,满足
第四个子任务共 17 分,满足
第五个子任务共 21 分,满足
第六个子任务共 29 分,满足
感谢@larryzhong 提供的加强数据
Solution
首先需要知道的是,分割的先后顺序并不会影响答案,比如题目中的分割方式是 1 3 5
,如果按照 5 3 1
分割,得到的答案是相同的,感兴趣的话可以尝试证明一下。
因此设
注意到转移的时候
但是这样虽然空间变成了
观察
因为我们要取一个最优的
对此不等式变形:
注意到不等号左边可以变成类似于斜率公式
需要注意的是,因为每一个数是非负的,因此
对于方案的输出,只需要在转移的时候记录下每次转移的来源即可,输出的时候一路循着输出即可。
时间复杂度是
此题有点卡常,对于滚动数组最好不要在每次滚动的时候模拟滚动的过程
Code
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
k=0;T flag=1;char b=getchar();
while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
while (isdigit(b)) {k=k*10+b-48;b=getchar();}
k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=1e5,INF=1e18;
int k,n;
int a[_SIZE+5],s[_SIZE+5],fa[205][_SIZE+5];
int f[2][_SIZE+5],cur=0,last=1;//f用来滚动,cur指向当前dp数组,last指向上一个dp数组
int q[_SIZE+5],head=1,tail=1;//单调队列(双端队列),head,tail前闭后开
double slope(int x,int y)//求两点斜率
{
int x1=-s[x],y1=f[last][x]-s[x]*s[x];
int x2=-s[y],y2=f[last][y]-s[y]*s[y];
if (x1==x2) return -INF;
return (y2-y1)*1.0/(x2-x1);
}
signed main()
{
read(n),read(k);
for (int i=1;i<=n;i++) read(a[i]),s[i]=a[i]+s[i-1];
for (int t=1;t<=k;t++)
{
cur^=1,last^=1;
head=tail=0;
for (int i=1;i<=n;i++)
{
while (tail-head>=2 && slope(q[head],q[head+1])<=s[i]) head++;//踢队头
f[cur][i]=0;
if (head<tail)//可以转移
{
int j=q[head];fa[t][i]=j;//记录来源
f[cur][i]=f[last][j]+s[j]*(s[i]-s[j]);//按照方程转移
}
while (tail-head>=2 && slope(q[tail-1],q[tail-2])>=slope(i,q[tail-1])) tail--;//踢队尾
q[tail++]=i;//入队
}
}
writewith(f[cur][n],'\n');
for (int x=fa[k][n];k;x=fa[--k][x]) writewith(x,' ');//输出方案
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步