1. 程式人生 > >尋找段落-洛谷-P1419

尋找段落-洛谷-P1419

原題: 給定一個長度為n的序列a_i,定義a[i]為第i個元素的價值。現在需要找出序列中最有價值的“段落”。段落的定義是長度在[S,T]之間的連續序列。最有價值段落是指平均值最大的段落,段落的平均值=段落總價值/段落長度。 輸入輸出格式 輸入格式: 第一行一個整數n,表示序列長度。 第二行兩個整數S和T,表示段落長度的範圍,在[S,T]之間。 第三行到第n+2行,每行一個整數表示每個元素的價值指數。 輸出格式: 一個實數,保留3位小數,表示最優段落的平均值。 輸入輸出樣例 輸入樣例#1: 3 2 2 3 -1 2 輸出樣例#1: 1.000 說明 【資料範圍】 對於30%的資料有n<=1000。 對於100%的資料有n<=100000,1<=S<=T<=n,-10000<=價值指數<=10000。 【題目來源】 tinylic改編 題意: 有一個長度為n的數列,找出其中長度在s-t內的平均值最大數列,輸出最大平均值。 題解: 一看題目,開始想到了移動視窗,結果TLE了。 之後想到了二分,但是也WA和TLE了幾個點。 二分思想很簡單,這道題主要學習的是這個判斷函式check: 對於區間內的所有值我們可以都先減去初始平均值m,之後再判斷若是在題目要求的範圍內存在區間的和大於0,則證明存在更大的平均值,反之則平均值取大了,沒有一個區間能夠產生這麼大的平均值。 這裡用到了字首和來記錄區間內的和。 附上AC程式碼:

#include <iostream>
#include <iomanip>
using namespace std;
int n,s,t,a[100005],q[100005];//q陣列用來記錄字首和的下標
double sum[100005];//字首和記錄到第i個的減去平均值的和
bool check(double m)
{
    sum[0]=0;
    for(int i=1;i<=n;i++)
    {
        sum[i]=sum[i-1]+a[i]-m;//初始化
    }
    int head=1,tail=0;//head為區間的左端點,tail為區間的右端點。
    for(int i=1;i<=n;i++)
    {
        if(i>=s)
        {
            while(head<=tail&&sum[i-s]<sum[q[tail]])//如果左端點小於等於右端點並且右端點的字首和大於最前的字首和那麼右端點減一
                tail--;//單調佇列維護
            q[++tail]=i-s;//對於每一個i都將他的右端點初始化為i-s;
        }
        if(head<=tail&&q[head]<i-t)//左端點小於上限,左端點加一;
            head++;
        if(head<=tail&&sum[i]-sum[q[head]]>=0)//如果這一段區間內的和大於0,則證明有更大的平均值存在。
            return true;
    }
    return false;
}
int main()
{
    cin>>n>>s>>t;
    for(int i=1;i<=n;++i)
    {
        cin>>a[i];
    }
    double l=-10005,m,r=10005;
    while(l+1e-5<r)
    {
        m=(l+r)/2;
        if(check(m))
            l=m;
        else
            r=m;
    }
    cout <<fixed<<setprecision(3)<<l<< endl;//輸出要求格式為小數點後三位
    return 0;
}