• Average and Median(二分、状态机DP)


    题意

    给定一个长度为\(n\)的序列,你要从中选择一些数字。要求相邻两个数字必须至少选择一个。求:

    • 选择出来的数字平均值最大可能是多少
    • 选择出来的数字中位数最大可能是多少

    数据范围

    \(2 \leq n \leq 100000\)

    思路

    考虑二分查找答案。

    • 对于平均值,假设当前查找的答案是\(K\),那么对序列的每个元素减去\(K\)。然后问题转化为是否能从序列中选择一些数字,使得这些数字之和是否非负。
    • 对于中位数,假设当前查找的答案是\(K\),那么对序列的每个元素,如果元素大于等于\(K\),那么设为\(1\);如果小于\(K\),那么设为\(-1\)。然后问题转化为是否能从序列中选择一些数字,使得这些数字之和大于\(0\)

    对于以上两个问题,其实就是从序列中选出一些数字,这些数字之和的最大值可能是多少。对于这个问题,考虑DP。设\(f_{i, 0}\)表示从前\(i\)个元素中选,且第\(i\)个元素必须选,所选元素和的最大值;\(f_{i, 1}\)表示从前\(i\)个元素中选,且第\(i\)个元素不选,所选元素和的最大值。那么转移方程就是:\(f_{i, 0} = \max\{f_{i - 1, 0}, f_{i - 1, 1}\}\)\(f_{i, 1} = f_{i - 1,1}\)

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int N = 100010;
    const double eps = 1e-6;
    
    int n;
    ll a[N];
    double b[N];
    int c[N];
    double f[N][2];
    int g[N][2];
    
    bool check1(double x)
    {
        for(int i = 1; i <= n; i ++) b[i] = a[i] - x;
        for(int i = 1; i <= n; i ++) {
            for(int j = 0; j < 2; j ++) {
                f[i][j] = 0;
            }
        }
        f[1][0] = b[1];
        for(int i = 1; i <= n; i ++) {
            f[i][0] = max(f[i - 1][0], f[i - 1][1]) + b[i];
            f[i][1] = f[i - 1][0];
        }
        return max(f[n][0], f[n][1]) >= eps;
    }
    
    bool check2(int x)
    {
        for(int i = 1; i <= n; i ++) {
            if(a[i] < x) c[i] = -1;
            else c[i] = 1;
        }
        for(int i = 1; i <= n; i ++) {
            for(int j = 0; j < 2; j ++) {
                g[i][j] = 0;
            }
        }
        g[1][0] = c[1];
        for(int i = 1; i <= n; i ++) {
            g[i][0] = max(g[i - 1][0], g[i - 1][1]) + c[i];
            g[i][1] = g[i - 1][0];
        }
        return max(g[n][0], g[n][1]) > 0;
    }
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
        double l = 1, r = 1e9;
        while(r - l > eps) {
            double mid = (l + r) / 2;
            if(check1(mid)) l = mid;
            else r = mid;
        }
        printf("%.6f\n", r);
        int l2 = 1, rr = 1e9;
        while(l2 < rr) {
            int mid = l2 + rr + 1 >> 1;
            if(check2(mid)) l2 = mid;
            else rr = mid - 1;
        }
        printf("%d\n", rr);
        return 0;
    }
    
  • 相关阅读:
    OpenJTAG与Jlink/Jlink的区别
    Ubunut 10.04 upgrade git
    HEVC播放器出炉,迅雷看看支持H.265
    平铺文理+拉伸按钮图片
    黑马程序员java基础学习IO流4
    Excel 2010实战技巧精粹
    昆明虚假楼盘吸引 2 千多人购买诈骗近 3 亿
    Canonical 公司预计最晚明年 4 月推出 Ubuntu 中国版
    Canonical 公司预计最晚明年 4 月推出 Ubuntu 中国版
    十二个理由让你不得不期待 Ubuntu10.10
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/16012819.html
Copyright © 2020-2023  润新知