zoukankan      html  css  js  c++  java
  • 最大K段和

    题目大意

    有一个长度为 (N) 的序列 (A) 。他希望从中选出不超过 (K) 个连续子段,满足它们两两不相交,求总和的最大值(可以一段也不选,答案为 (0))。

    分析

    很容易想到 (O(n^2))(dp)
    (f[i][j]) 表示选到第 (i) 位,已选了 (j) 段时的最大答案
    那么 (f[i][j] = max(f[i-1][j] , s[i] + max_limits{0<l<i}(f[l][j-1] - s[l])))
    然后维护最大的 (f[l][j-1]-s[l])(O(1)) 更新即可
    然后我们可以想到 (WQS) 二分(虽然我想不到)
    它大概就是解决:有 (n) 个带权物品,用满足一定限制的方法选 (m) 个,使得其权值和取最值,而且权值和的最值是关于 (m) 的凸函数
    注意 (x) 是段数
    用直线 (y=kx + b) 去切
    因为我们要求最大值,所以要最大化 (b)
    (b=y-kx)
    那么我们就可以将原来的 (dp) 是改为
    (f[i]=max(f[i-1] , s[i] - k + max_limits{0<l<i}(f[l]-s[l])))

    总的来说,先二分 (k),然后判断就 (dp),并记录所分的段数
    段数恰为 (m) 时就为答案
    注意最后要以 (k=ans)(dp) 一遍

    (Code)

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    using namespace std;
    typedef long long LL;
    
    const int N = 1e5 + 5;
    int n , k , a[N];
    LL f[N] , g[N] , s[N] , l , r , mid , ans;
    
    bool check()
    {
    	int x = 0;
    	for(register int i = 1; i <= n; i++)
    	{
    		f[i] = f[i - 1] , g[i] = g[i - 1];
    		if (f[x] + s[i] - s[x] - mid > f[i])
    			f[i] = f[x] + s[i] - s[x] - mid , g[i] = g[x] + 1;
    		if (f[i] - s[i] > f[x] - s[x]) x = i;
    	} 
    	return g[n] >= k;
    }
    
    int main()
    {
    	freopen("maxksum.in" , "r" , stdin);
    	freopen("maxksum.out" , "w" , stdout);
    	scanf("%d%d" , &n , &k);
    	for(register int i = 1; i <= n; i++) 
    		scanf("%d" , &a[i]) , s[i] = s[i - 1] + a[i];
    	r = 0x3f3f3f3f3f3f3f3f;
    	while (l <= r)
    	{
    		mid = (l + r) >> 1;
    		if (check()) ans = mid , l = mid + 1;
    		else r = mid - 1;
    	}
    	mid = ans , check();
    	printf("%lld" , f[n] + ans * k);
    }
    
  • 相关阅读:
    20165229预备作业三
    20165529学习基础和C语言基础调查
    20165229我所期待的师生关系
    20165210 Java第七周学习总结
    20165210 Java第六周学习总结
    20165210 Java第一次实验报告
    20165210 Java第五周学习总结
    20165210 Java第四周学习总结
    20165210 Java第三周学习总结
    20165210 Java第二周学习总结
  • 原文地址:https://www.cnblogs.com/leiyuanze/p/13860835.html
Copyright © 2011-2022 走看看