Part 0(P10的写法,巨佬请忽略)
可以发现其实只用知道分成那几个块即可,不用知道中途分裂的顺序。
记R[i]为第i个元素后面最近的裂痕在哪个元素的后面(若这个元素后面无裂痕就为n+1),sum[i]为1~i的元素的和,那么第i个元素对答案的贡献就是 v a l [ i ] ∗ ( s u m [ n ] − s u m [ R [ i ] ] ) val[i]*(sum[n]-sum[R[i]]) val[i]∗(sum[n]−sum[R[i]])。答案就是每个元素对答案的贡献之和。
故第一档的写法就是二进制枚举裂痕+算答案。
Part 1(P4的写法)
这一档和上一档的方法又一样。不过这档是正解的基础。
我们从左到右枚举裂痕。此时我们只用知道上一个裂痕在哪就可算出本次分裂所增加的答案。这个可以用动态规划。我们定义 d p [ i ] [ j ] dp[i][j] dp[i][j]的含义为第j条裂痕(从左向右数第j条)在i这个位置,分裂成j+1个区间时产生的最大答案。
状态转移方程为:
d
p
[
n
o
w
]
[
c
n
t
]
=
max
(
d
p
[
L
s
t
]
[
c
n
t
−
1
]
+
(
s
u
m
[
n
o
w
]
−
s
u
m
[
L
s
t
]
)
∗
(
s
u
m
[
n
]
−
s
u
m
[
n
o
w
]
)
)
dp[now][cnt]=\max(dp[Lst][cnt-1]+(sum[now]-sum[Lst])*(sum[n]-sum[now]))
dp[now][cnt]=max(dp[Lst][cnt−1]+(sum[now]−sum[Lst])∗(sum[n]−sum[now]))
记录方案就是记录这个状态从哪个状态转移而来,输出时就一直往回跳即可。
代码:
long long dp[1005][205];
int Pre[1005][205];
long long sum[1005];
long long Get_sum(int L,int R){
return sum[L]-sum[R+1];
}
void Solve(){
memset(Pre,0,sizeof(Pre));
long long ans=0;
int ans_step;
sum[n+1]=0;
for(int i=n;i>=1;i--)sum[i]=sum[i+1]+val[i];//这里是处理后缀和
memset(dp,0,sizeof(dp));
for(int now=1;now<n;now++){
for(int cnt=0;cnt<K;cnt++){
bool flag=false;//这个主要是怕有一个答案==0
for(int Lst=cnt==0?0:now-1;Lst>=0;Lst--){//枚举上一个裂痕在哪
long long nxt_ans=dp[Lst][cnt]+1ll*Get_sum(Lst+1,now)*sum[now+1];//计算答案
if(nxt_ans>dp[now][cnt+1]||!flag){
dp[now][cnt+1]=nxt_ans;
Pre[now][cnt+1]=Lst;
}
flag=true;
if(cnt==K-1&&dp[now][cnt+1]>ans){
ans=dp[now][cnt+1];
ans_step=now;
}
}
}
}
printf("%lld\n",ans);
for(int i=K;i>=1;i--){
printf("%d ",ans_step);
ans_step=Pre[ans_step][i];
}
puts("");
}
Part2 正解
我们观察那个式子,把它转化一下。
d
p
[
n
o
w
]
[
c
n
t
]
=
max
(
d
p
[
L
s
t
]
[
c
n
t
−
1
]
−
s
u
m
[
L
s
t
]
∗
(
s
u
m
[
n
]
−
s
u
m
[
n
o
w
]
)
)
+
s
u
m
[
n
o
w
]
∗
(
s
u
m
[
n
]
−
s
u
m
[
n
o
w
]
)
dp[now][cnt]=\max(dp[Lst][cnt-1]-sum[Lst]*(sum[n]-sum[now]))+sum[now]*(sum[n]-sum[now])
dp[now][cnt]=max(dp[Lst][cnt−1]−sum[Lst]∗(sum[n]−sum[now]))+sum[now]∗(sum[n]−sum[now])
如果我们进行一下定义
s
u
m
[
L
s
t
]
=
x
k
=
s
u
m
[
n
]
−
s
u
m
[
n
o
w
]
y
=
d
p
[
L
s
t
]
[
c
n
t
−
1
]
那
么
由
于
b
=
y
−
k
x
d
p
[
n
o
w
]
[
c
n
t
]
=
m
a
x
(
b
)
+
s
u
m
[
n
o
w
]
∗
(
s
u
m
[
n
]
−
s
u
m
[
n
o
w
]
)
sum[Lst]=x\\ k=sum[n]-sum[now]\\ y=dp[Lst][cnt-1]\\ 那么由于b=y-kx\\ dp[now][cnt]=max(b)+sum[now]*(sum[n]-sum[now])
sum[Lst]=xk=sum[n]−sum[now]y=dp[Lst][cnt−1]那么由于b=y−kxdp[now][cnt]=max(b)+sum[now]∗(sum[n]−sum[now])
然后就可以用斜率优化了。(斜率优化的入门)。本题要维护一个上凸包(斜率递减)(可分类讨论发现下凸包中间的点都没有两边的点更优)。对于点A和点B(假设点A的x比B小),只有当A–B的斜率大于k时B才比A更优。
由于本题枚举是k单调递减。故当B比A更优之后,只后的k不会使A比B更优,故可以维护一个单调队列一直弹左端点即可。
代码:(有解释)
#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 100005
#define lowbit(x) x&-x
using namespace std;
int n,K;
int val[M];
struct node{
int id;
long long x,y;
};
int Pre[M][205];//计算这个状态是由哪个状态转移而来
long long sum[M],dp[2][M];//这里用滚动数组
node stk[M];
int L,R;
long long Calc(node A,long long k){//计算b(截距)
return 1ll*A.y-1ll*A.x*k;
}
bool check(node A,node B,node C){//如果A--C的斜率大于A--B的斜率
return 1.0*(C.y-A.y)*(B.x-A.x)>=1.0*(B.y-A.y)*(C.x-A.x);
}
void Push(node New){//加入一个新的点
while(L<R&&check(stk[R-1],stk[R],New))R--;//维护上凸包
stk[++R]=New;
}
bool cmp(node A,node B,long long K){//B比A更优
return 1ll*A.y-K*A.x<=1ll*B.y-K*B.x;
}
int Get_mx(long long K){
while(L<R&&cmp(stk[L],stk[L+1],K))L++;//弹左端点
return L;
}
void Solve(){
long long ans=0;
int ans_step;
sum[0]=0;
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+val[i];//前缀和
for(int i=1;i<=n;i++){
dp[0][i]=1ll*sum[i]*(sum[n]-sum[i]);
if(K==1)ans=max(ans,dp[0][i]),ans_step=i;//特判K=1的情况
}
for(int cnt=2,cur=0;cnt<=K;cnt++,cur=!cur){
L=R=0;
Push((node){cnt-1,sum[cnt-1],dp[cur][cnt-1]});//由于放cnt-1个裂痕时最后一个裂痕只能放在第cnt-1个位置之后的位置
for(int now=cnt;now<n;now++){
int flag=Get_mx(sum[n]-sum[now]);//这里返回的是一个id
Pre[now][cnt]=stk[flag].id;
dp[!cur][now]=Calc(stk[flag],sum[n]-sum[now])+1ll*(sum[n]-sum[now])*sum[now];//计算答案
Push((node){now,sum[now],dp[cur][now]});
if(ans<dp[!cur][now]&&cnt==K){//更新答案
ans_step=now;
ans=dp[!cur][now];
}
}
}
printf("%lld\n",ans);
for(int i=K;i>=1;i--){//输出方案
printf("%d ",ans_step);
ans_step=Pre[ans_step][i];
}
puts("");
}
int main(){
scanf("%d%d",&n,&K);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
Solve();
return 0;
}