【UOJ #104】【APIO 2014】Split the sequence

http://uoj.ac/problem/104
此题的重点是答案只与切割的最终形态有关,与切割顺序无关。
\(f(i,j)\)表示前\(i\)个元素切成\(j\)个能产生的最大贡献。
\(f(i,j)=\max\{f(k,j-1)+sum(k+1,i)(sum(1,n)-sum(k+1,i)),k<i\}\),其中\(sum(l,r)=\sum\limits_{i=l}^ra_i,sum_k=\sum\limits_{i=1}^ka_i\)
有决策点\(k\)\(l\),当\(k<l\)\(l\)\(k\)更优时,化出斜率优化dp的式子:\[-2sum_i<\frac{\left(f(l,j-1)-sum_nsum_l-sum_l^2\right)-\left(f(k,j-1)-sum_nsum_k-sum_k^2\right)}{sum_l-sum_k}\]
可以把每个决策点\(k\)看成横坐标为\(sum_k\),纵坐标为\(f(l,j-1)-sum_nsum_l-sum_l^2\)的点,每次用斜率为\(-2sum_i\)的直线平移到这个点上,找纵截距最大的直线经过的点作为决策点。
决策点一定在上凸壳,因为\(sum_i\)单调不降,用单调栈维护上凸壳即可。
又因为\(-2sum_i\)单调不升,具有决策单调性,把单调栈改成双端队列,每次不断从队首弹出不够优的决策点。
时间复杂度\(O(nk)\)
为什么uoj上的样例本机AC提交RE???删掉inline还T???卡常数!手动把函数压倒主函数里,快速读入,还有二维数组把较小的一维放在前面,这个优化效果最明显!

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int in() {
    char c = getchar(); int k = 0;
    for (; c < '0' || c > '9'; c = getchar());
    for (; c >= '0' && c <= '9'; c = getchar())
        k = k * 10 + (c ^ 48);
    return k;
}

const int N = 100003;
const int K = 203;

int n, k, pre[K][N], a[N], head, tail, qu[N];
ll f[K][N], S, sum[N], qucal[N];

ll calzj;
int cut[N], cuttot = 0;

int main() {
    n = in(); k = in();
    for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + (a[i] = in());
    S = sum[n];
    
    for (int i = 1; i <= n; ++i)
        f[1][i] = 1ll * sum[i] * (S - sum[i]);
    
    for (int j = 2; j <= k + 1; ++j) {
        head = 0; tail = 0; qu[0] = 0;
        for (int i = 1; i <= n; ++i) {
            while (head < tail && qucal[head + 1] - qucal[head] >= (sum[qu[head + 1]] - sum[qu[head]]) * (sum[i] * -2)) ++head;
            ll sumlr = sum[i] - sum[qu[head]];
            f[j][i] = f[j - 1][qu[head]] + sumlr * (S - sumlr);
            pre[j][i] = qu[head];
            calzj = f[j - 1][i] - S * sum[i] - sum[i] * sum[i];
            while (head < tail && (sum[qu[tail]] == sum[i] ? (calzj >= qucal[tail]) : (calzj - qucal[tail]) * (sum[qu[tail]] - sum[qu[tail - 1]]) > (qucal[tail] - qucal[tail - 1]) * (sum[i] - sum[qu[tail]]))) --tail;
            if (sum[qu[tail]] != sum[i]) qu[++tail] = i, qucal[tail] = calzj;
            else if (head == tail && calzj >= qucal[head]) qu[tail] = i, qucal[tail] = calzj;
        }
    }
    
    printf("%lld\n", f[k + 1][n] >> 1);
    int tmp = n;
    for (int i = k + 1; i > 1; --i)
        tmp = cut[++cuttot] = pre[i][tmp];
    for (int i = cuttot; i >= 1; --i) printf("%d ", cut[i]);
    
    return 0;
}

转载于:https://www.cnblogs.com/abclzr/p/6723784.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值