Luogu-3648 [APIO2014]序列分割
题解:
首先要发现一个重要的性质:分割的顺序是不会影响答案的
证明:
首先对于没有交的两段区间,显然先后顺序改变不会有影响
而对于在同一段区间上的两次分割:
设有一段序列由长度为(x,y,z)的三段拼接起来
如果先分割(xy)和(z),再分割(x)和(y),答案是((x+y)*z+x*y)
而如果先分割(x)和(yz),再分割(y)和(z),答案是(x*(y+z)+y*z)
可以发现,两个答案都是(xy+xz+yz),即分割顺序不影响答案
这样我们就可以愉快地DP啦
设(f[i][j])为将(1sim i)这段分割(j)次产生的代价,(s[i])为(1sim i)段的总长,则有:
[f[i][j]=f[k][j-1]+s[k]*(s[i]-s[k]) quad kin [1,i-1]
]
分割次数这一维可以滚动优化空间,设上一次的为(g[i]),这一次要求的是(f[i])
上面的式子就是
[f[i]=g[k]+s[k]*(s[i]-s[k]) quad kin [1,i-1]
]
哎,感觉很像可以斜率优化的式子啊,那么...
如果从(k)转移比从(j)转移更优,则:
[g[k]+s[k]*(s[i]-s[k]) > g[j]+s[j]*(s[i]-s[j])
]
再化一化
[frac{(g[k]+s[k]*s[k])-(g[j]-s[j]*s[j])}{s[j]-s[k]} >s[i]
]
于是我们就可以用斜率优化dp啦,
注意这题精度卡的简直丧心病狂...算斜率的时候一定要先算减再乘(1.0)
代码
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
inline ll read(){
ll ans=0,fh=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
return ans*fh;
}
const int maxn=1e5+100;
const long double inf=1e18;
int n,k,st[maxn],tp,pre[210][maxn],q[maxn];
ll s[maxn],f[maxn],g[maxn];
inline long double slope(int x,int y){
if(s[x]==s[y]) return -inf;
long double aa=1.0*((g[x]-s[x]*s[x])-(g[y]-s[y]*s[y]));
return aa/(s[y]-s[x]);
}
int main(){
// freopen("nh.in","r",stdin);
//freopen("zhy.out","w",stdout);
n=read(),k=read();
for(int i=1;i<=n;i++) s[i]=read()+s[i-1];
for(int i=1;i<=k;i++){
int l=1,r=0;q[++r]=0;
for(int j=1;j<=n;j++){
while(l<r&&slope(q[l],q[l+1])<=s[j]) l++;
f[j]=g[q[l]]+s[q[l]]*(s[j]-s[q[l]]);
pre[i][j]=q[l];
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],j)) r--;
q[++r]=j;
}
memcpy(g,f,sizeof(g));
}
printf("%lld
",g[n]);
for(int i=pre[k][n];i;i=pre[--k][i]) st[++tp]=i;
while(tp) printf("%d ",st[tp--]);
return 0;
}