【算法学习笔记】52.一道题的三种方法..二分答案、动态规划、计算几何 SJTU OJ 1250 BestSubsequence

---恢复内容开始---

1250. BestSubsequence

Description

LL有n个妹子,他给妹子们编号排成一排。据说今天天气大好,LL要去春游了,他决定要选定至少F个妹子一起去玩。 为了让妹子们开心,他决定选连续一段的妹子们。然后LL有个特殊的癖好,他喜欢体重比较厉害一些的妹子。

那你可以帮LL选妹子吗,使得选出来的这些妹子的平均体重最大。

Input Format

输入第一行两个整数,n和F。

接下来n行,每行一个整数,表示妹子的体重。

对前50%的数据:1<=n<=20。 对100%的数据:1<=n<=100000, 1<=F<=n, 1<=妹子体重<=2000。

Output Format

输出一个整数,为这个平均值乘以1000,不要四舍五入,输出int(avergae * 1000)即可。

Sample Input

10 6
6
4
2
10
3
8
5
9
4
1

Sample Output

6500

题目大意:给出一个序列,长度为N,均为正数。找出一段连续的区间,此区间的平均值最大,长度必须大于等于F。

1.二分答案法 O(nlgM) 

首先留意到答案的输出是整数。想到答案的范围是可以根据输入确定的,而且范围较小(M<=2000)所以可以用二分答案法 先猜测一个平均数A,来判断是否存在一个长度大于等于F的连续序列可以满足平均值大于A。判断的方法可以利用一个成型的DP方法来解决这个问题,那就是寻找一个序列是否存在一个长度大于等于F的子序列和为正(每个数减去平均值即可)。dp的状态转移方程就是:f[a, b] 表示在区间 [a, b] 中,所有子区间的最大值,最后只要判断 Then当 b - a = F 时,f[a, b] 为序列中对应的和。当 b - a > F 时,f[a, b] = max{ f[a, b - 1] + arr[b], f[b - f + 1, b] }

注意第二种情况下,如果遇到了以b结尾的连续F个元素的子序列的和更大一些,那就从此开始重新累加计算。

而且注意,二分法的时候一定要用double来二分 输出R while(L-R>1e-7) R=mod L=mod 都是关键点

代码如下:

#include <iostream>
#include <cstdio>
using namespace std;
const int MaxN = 100000+10;
int n,F;
int weight[MaxN];
int preSum[MaxN];
int Min;
int Max;

void Init(){
    cin>>n>>F;
    cin>>weight[1];
    preSum[0] = 0;
    Min = Max = preSum[1] = weight[1];
    for (int i = 2; i <= n; ++i)
    {
        scanf("%d",&weight[i]);
        preSum[i] = preSum[i-1] + weight[i];
        Max = Max > weight[i] ? Max : weight[i];
        Min = Min < weight[i] ? Min : weight[i];
    }
    return;
}
//因为Guess是由猜测确定的 它可能是F个 也可能是>F个数的avg 所以还是要考虑所有的情况
inline bool BinarySearch(double guess){ 

    double pre = 0.0;
    double cur = 0.0;

    //pre初始为前n-1项的 去掉guess的和
    //pre 其实就是用来检查是否大于0的连续的那段
    pre = (preSum[F-1] - preSum[0]) - guess * (F-1);
    //从第F项开始遍历

    for (int i = F; i <= n ; ++i)
    {
        //cur是某F项的 减去guess的和
        cur = preSum[i] - preSum[i-F] - guess * F;
        //pre 第一次循环是前F项的和
        pre += weight[i] - guess;

        //核心思想就是 如果整个pre还不如后F个大 那就要后F个即可
        if(cur > pre)
            pre = cur;
        if(pre > -1e-6)
            return true;
    }

    return false;

}

int main(int argc, char const *argv[])
{
    Init();
    //注意因为真正二分的答案是去分析那个double的平均值 所以还是尽量用double 否则可能会出现刻度不够精细导致的错误
    double L = Min;
    double R = Max;
    while(R - L >= 1e-6){
        double mid = (L+R)/2;

        //double类型的二分法
        if(BinarySearch(mid)){ //说明这个猜测比较小
            L = mid;
        }else{
            R = mid;
        }
    }
    //注意这里必须输出R
    cout<<int(R*1000)<<endl;
    return 0;
}

二分答案法

  2.动态规划法 O(n)

核心的思想就是

先取前F-1个元素

分别维护i和j,认为 [j,i+F]就是答案

核心的思想很简单 就是如果j到i这一段的平均值小于i,到i+F这一段的平均值 则不要前半段了.

#include <iostream>
#include <cstdio>
using namespace std;
const int MaxN = 100000+10;
//求最大平均的连续子列合 (至少连续F个元素) 考虑动态规划

int weight[MaxN]={0};//表示每个妹子的体重
int preSum[MaxN];//前缀和

int main(int argc, char const *argv[])
{

    int n,F;
    cin>>n>>F;
    preSum[0] = 0;
    for (int i = 1; i <= n; ++i){
        scanf("%d",&weight[i]);
        preSum[i] = preSum[i-1] + weight[i];
    }
    int ans = -1;
    //最后toCheck的部分是 j 到 i+F 的这段
    //核心思想就是 如果j到i这段是拉低了整个toCheck的部分 不如就不要了
    //上面这一个核心思想非常像二分答案里 判断是否存在大于等于F项连续和大于0的思想
     for (int i = 0, j = 0; i <= n - F; i++)
    {
        if (i > j && (preSum[i] - preSum[j]) * (i + F - j) < (preSum[i + F] - preSum[j]) * (i - j))
            j = i;//如果i和j之间的子段平均值小于i到i+f之间的平均值,则将舍弃i和j之间的子段
        if (ans < 1000 * (preSum[i + F] - preSum[j]) / (i + F - j))
            ans = 1000 * (preSum[i + F] - preSum[j]) / (i + F - j);
    }
    cout<<ans<<endl;
    return 0;
}  

动态维护

时间: 2024-10-11 22:45:15

【算法学习笔记】52.一道题的三种方法..二分答案、动态规划、计算几何 SJTU OJ 1250 BestSubsequence的相关文章

【C语言学习笔记】字符串拼接的3种方法 .

昨天晚上和@buptpatriot讨论函数返回指针(malloc生成的)的问题,提到字符串拼接,做个总结. [cpp] view plaincopyprint? #include<stdio.h> #include<stdlib.h> #include<string.h> char *join1(char *, char*); void join2(char *, char *); char *join3(char *, char*); int main(void) {

尚硅谷springboot学习6-eclipse创建springboot项目的三种方法(转)

方法一 安装STS插件 安装插件导向窗口完成后,在eclipse右下角将会出现安装插件的进度,等插件安装完成后重启eclipse生效 新建spring boot项目 项目启动 方法二 1.创建Maven项目 2.选择项目类型 3.选择项目 4.编写项目组和名称-finish即可 5.修改pom.xml文件 <!-- spring boot基本环境 --> <parent> <groupId>org.springframework.boot</groupId>

清空StringBuilder的三种方法及效率

清空StringBuilder的三种方法及效率 大家知道对于字符串频繁拼接是使用stringbuilder.Append方法比使用string+=方法效率高很多,但有时需要清空stringbuilder时却不知道怎么清空,因为它没有clear或empty的方法.那用什么方法呢?在网上搜了一下大概一下三种方法. 1.Remove 例: StringBuilder val = new StringBuilder(); val.Append("...."); val.Remove(0,val

由LCS到编辑距离—动态规划入门—算法学习笔记

一切计算机问题,解决方法可以归结为两类:分治和封装.分治是减层,封装是加层. 动态规划问题同样可以用这种思路,分治. 它可以划分为多个子问题解决,那这样是不是用简单的递归就完成了?也许是的,但是这样会涉及太多的不便的操作.因为子问题有重叠! 针对这种子问题有重叠的情况的解决,就是提高效率的关键. 所以动态规划问题可以总结为:最优子结构和重叠子问题. 解决这个子问题的方式的关键就是:memoization,备忘录. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方

Python—kmeans算法学习笔记

一.   什么是聚类 聚类简单的说就是要把一个文档集合根据文档的相似性把文档分成若干类,但是究竟分成多少类,这个要取决于文档集合里文档自身的性质.下面这个图就是一个简单的例子,我们可以把不同的文档聚合为3类.另外聚类是典型的无指导学习,所谓无指导学习是指不需要有人干预,无须人为文档进行标注. 二.聚类算法:from sklearn.cluster import KMeans def __init__(self, n_clusters=8, init='k-means++', n_init=10,

算法学习笔记 递归之 快速幂、斐波那契矩阵加速

递归的定义 原文地址为:http://blog.csdn.net/thisinnocence 递归和迭代是编程中最为常用的基本技巧,而且递归常常比迭代更为简洁和强大.它的定义就是:直接或间接调用自身.经典问题有:幂运算.阶乘.组合数.斐波那契数列.汉诺塔等.其算法思想: 原问题可分解子问题(必要条件): 原与分解后的子问题相似(递归方程): 分解次数有限(子问题有穷): 最终问题可直接解决(递归边界): 对于递归的应用与优化,直接递归时要预估时空复杂度,以免出现用时过长或者栈溢出.优化递归就是以

EM算法学习笔记2:深入理解

文章<EM算法学习笔记1:简介>中介绍了EM算法的主要思路和流程,我们知道EM算法通过迭代的方法,最后得到最大似然问题的一个局部最优解.本文介绍标准EM算法背后的原理. 我们有样本集X,隐变量Z,模型参数θ,注意他们3个都是向量,要求解的log似然函数是lnp(X|θ),而这个log似然函数难以求解,我们假设隐变量Z已知,发现lnp(X,Z|θ) 的最大似然容易求解. 有一天,人们发现引入任意一个关于隐变量的分布q(Z),对于这个log似然函数,存在这样一个分解: lnp(X|θ)=L(q,θ

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前

Sharepoint2013搜索学习笔记之修改搜索拓扑(三)

搜索服务新建好之后可以从管理中心,应用程序管理页面,进入搜索服务的管理页面,进入管理页面之后可以看到当前sharepoint场的搜索拓扑结构. 如果sharepoint场内有多台服务器,需要将搜索组件部署到这些服务器上,可以在装有sharepoint管理中心的服务器上启动sharepoint命令行管理程序,通过口令的方式对搜索拓扑进行更改. 注意: 向新的服务器添加搜索组件之前,必须先启动新增服务器上的搜索服务实例.搜索服务实例将启动搜索服务(OSearch15 和 SPSearchHostCo