【bzoj3675】 Apio2014—序列分割
http://www.lydsy.com/JudgeOnline/problem.php?id=3675 (题目链接)
题意
给出一个包含n个非负整数的序列,要求将其分割成k+1个序列,每次分割可以获得一定的分数,分数=序列分割位置左侧的数之和×序列分割位置右侧的数之和。要求最大分数是多少。
Solution
稍加分析,发现其实最后得到的分数与分割的先后顺序无关,这个问题卡了我好久,我还是太辣鸡了→_→。发现最后得到的分数=序列1的数字之和×序列2的数字之和×·····×序列k+1的数字之和。
那么我们可以列出dp方程:${f[x][i]=max(f[x][i],f[x-1][j]+s[j]×(s[i]-s[j]))}$。其中${f[x][i]}$表示将区间${[1,i]}$的序列分割成当${x}$块所得到的最大分数,${s[i]}$表示${1~i}$的前缀和。可是这样的话复杂度就是${O(n*n*k)}$的了,所以我们需要斜率优化。
最后斜率式长这样:
$${\frac{f[j]-f[k]+s[k]^2-s[j]^2}{s[k]-s[j]}<s[i]}$$
所以当q[l]与q[l+1]满足上式时,就pop掉q[l]。
细节
注意f,s数组开long long,斜率的分母${s[k]-s[j]}$可能为0。
代码
// bzoj3675 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define inf 2147483600 #define Pi acos(-1.0) #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout); using namespace std; const int maxn=100010; LL s[maxn],f[2][maxn]; int a[maxn],q[maxn],n,m; double K(int k,int a,int b) { return s[b]-s[a]==0 ? 0 : (double)(f[k][a]-f[k][b]-s[a]*s[a]+s[b]*s[b])/(double)(s[b]-s[a]); } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i]); int x=0; for (int i=1;i<=n;i++) s[i]=s[i-1]+a[i]; for (int k=1;k<=m;k++) { x^=1; int l=1,r=1;q[1]=k-1; for (int i=k;i<=n;i++) { while (l<r && K(x^1,q[l],q[l+1])<s[i]) l++; f[x][i]=f[x^1][q[l]]+s[q[l]]*(s[i]-s[q[l]]); while (l<r && K(x^1,q[r-1],q[r])>K(x^1,q[r],i)) r--; q[++r]=i; } } printf("%lld",f[x][n]); return 0; }
This passage is made by MashiroSky.