zoukankan      html  css  js  c++  java
  • 【BZOJ】3675: [Apio2014]序列分割

    http://www.lydsy.com/JudgeOnline/problem.php?id=3675

    题意:给一个n个数字的序列,每一次分割的贡献是$sum(left, mid)*sum(mid+1, right)$,其中$left$表示本序列的最左边,$right$同理,$mid$是分割的位置(即在$mid$和$mid+1$中分割)。每次分割序列会变成两半。问分割k次得到的最大贡献和。n<=100000, k<=200

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=100005;
    ll s[N], d[2][N];
    int n, K, q[N], fr, ta;
    int main() {
    	scanf("%d%d", &n, &K);
    	for(int i=1; i<=n; ++i) scanf("%lld", &s[i]), s[i]+=s[i-1];
    	ll *now=d[0], *last=d[1];
    	for(int p=1; p<=K; ++p) {
    		fr=ta=0; q[ta++]=0;
    		for(int i=1; i<=n; ++i) {
    			while(fr!=ta-1 && last[q[fr]]-last[q[fr+1]]<=(s[q[fr]]-s[q[fr+1]])*(s[n]-s[i])) fr++;
    			int j=q[fr];
    			now[i]=last[j]+(s[i]-s[j])*(s[n]-s[i]);
    			while(fr!=ta-1 && (last[i]-last[q[ta-2]])*(s[q[ta-1]]-s[q[ta-2]])>=(last[q[ta-1]]-last[q[ta-2]])*(s[i]-s[q[ta-2]])) --ta;
    			q[ta++]=i;
    		}
    		swap(last, now);
    	}
    	ll ans=0;
    	for(int i=1; i<=n; ++i) ans=max(ans, last[i]);
    	printf("%lld
    ", ans);
    	return 0;
    }
    

      

    听laekov说要分析一下特殊的性质,于是分析了一下。。可以发现,每一个块对答案的贡献是$sum(本块)*sum(剩下的元素)$,最后当然要除以2,因为重复算了两次。但是可以用乱搞一下。。。

    考虑dp,设$d(i, j)$表示这个序列在$i$分割了$j$次得到的答案且第$j$次是在$i$分割的。

    容易得到:

    $d(i, j)=max(d(k, j-1)+sum(k+1, i)*sum(i+1, n))$

    大概就是表示得到的块为$(k+1, i)$,然后由于前面算过了对这些值的乘积,所以不用再计算一次(否则答案要除以二= =),于是我们直接乘一下后面的和即可。。

    然后另$s(n)=sum_{i=1}^{n} a[i]$,则

    $d(i, j)=max(d(k, j-1)+(s(i)-s(k))*(s(n)-s(i)))$

    然后搞搞斜率优化即可= =

    (好久没写了然后发现自己维护上凸壳时整个人sb了。。。。队尾居然不是维护凸壳然后交了4发wa居然没发现。。

  • 相关阅读:
    大道至简读后感
    机器学习十讲(一)
    第一个TensorFlow的简单例子
    初识深度学习
    如何使用本地的Navicat连接服务器中的Mysql
    阿里云ECS-安装Tomcat
    阿里云ECS-CentOS 8上安装MySQL 8.0
    阿里云ECS--CentOS8安装jdk1.8
    进度报告十(重大技术需求)
    进度报告九 (重大技术需求调研)
  • 原文地址:https://www.cnblogs.com/iwtwiioi/p/4488743.html
Copyright © 2011-2022 走看看