【CodeForces 626E】Simple Skewness (三分)

37 篇文章 0 订阅
24 篇文章 0 订阅

思路转自:http://www.cnblogs.com/flipped/p/5211693.html

给出n个数的集合,求一个 (平均数-中位数)最大 (偏度最大)的子集,输出子集元素个数和各个元素(任意顺序)。

分析

因为是子集,所以不一定是连续的序列。然后我们有下面几个结论。

1.最大偏度一定≥0

  因为一个元素时,偏度为0。

2.最大偏度子集必定有元素个数为奇数个的。

证:

如果当元素个数是偶数2*k时偏度最大,我们证明它去掉一个元素a[k+1]不会更差。

子集里排好序分别是a[i]。除去a[k+1]其它数的平均值为av

新平均值-旧平均值=av-(av+a[k+1])/2=(av-a[k+1])/2

新中位数-旧中位数=a[k]-(a[k]+a[k+1])/2=(a[k]-a[k+1])/2

且有 旧平均值-旧中位数=(av+a[k+1])/2-(a[k]+a[k+1])/2=(av-a[k])/2≥0 (否则不可能偏度最大)

所以有 平均值增量-中位数增量=(av-a[k])/2≥0

所以新的偏度肯定不会更差。

3.平均值先递增后递减

因为是奇数个,所以枚举每个数做中位数,假如左右延伸长度为j,那么要使偏度更大,我们一定是每次选剩下的里面左边最大和右边最大的数。所以剩下的数越来越小,平均值增加得越来越少,而当前平均值越来越大,到某个峰值后平均值就开始减小了。

所以可以用二分法每次取中点和中点旁边一个点判断当前平均值在增加还是减小,增加就往右找,减小就往左找。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<vector>
#include<queue>
#include<map>
#define inf 0x3f3f3f3f
using namespace std;

typedef long long ll;
const ll mod=1e9+7;
const int maxn=2e5+10;

ll sum[maxn],a[maxn];
int n;
ll cal(int len,int x)
{
    return sum[n]-sum[n-len] + sum[x]-sum[x-len-1];
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(sum, 0, sizeof(sum));
        ll ans = 0, ansx = 1, len = 0;
        for(int i = 1;i <= n ;i++)
            scanf("%lld",&a[i]);
        sort(a+1,a+1+n);
        for(int i = 1;i <=n ;i++)
            sum[i] = sum[i-1]+a[i];
        for(int i = 2;i <= n-1;i++)
        {
            int l = 0,r = min(i-1,n-i), mid, mmid, t = 200;
            while(l <= r)  //不是浮点数还是 l <= r吧
            {
                mid = l + (r-l)/3;
                mmid = r - (r-l)/3;
                ll tmp1 = cal(mid,i);
                ll tmp2 = cal(mmid,i);
                if(tmp1*(2*mmid+1) < tmp2*(2*mid +1))
                    l = mid+1;
                else
                    r = mmid-1;
            }
            ll res = cal(l,i) - (2*l+1)*a[i];
            if(res*(2*len+1) > ans*(2*l+1))
            {

                ans = res;
                len = l;
                ansx = i;
            }
        }
        printf("%lld\n",2*len+1);
        for(int i = ansx-len;i <= ansx;i++)
        printf("%lld ",a[i]);
        for(int i = n-len +1;i <= n;i++)
        printf("%lld ",a[i]);
        puts("");
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值