Codeforces Round #609 (Div. 1)C. K Integers(逆序对、二分、向中聚集)

前言:还行,推出了个大概,就差临门一脚。


题目传送门


  题目类型:逆序对、二分、向中聚集。
  解析: 很明显,求f(n)的时候就是求整个数组的逆序对。顺着这样的思路,f(k)就是把1到k聚集到一起,然后求1~k的逆序对。然后我是从fn求到f1的。
  那么题目分成两步了,第一步把每个数字对其后面数生成的逆序对数量找出来,记在suf[a[i]]里。
  第二步就是求聚集k个数的花费。最优解是最中间的数不动,其他数向中间靠拢。(偶数时中间两个都要试一下)
  如何找到最中间的呢?假设求f(k),现在数字分为两种,1到k是我们要聚集的,其他数是无用的。设前缀和pre[i]为1~i中有多少个需要聚集的数,若pre[i] = k/2+k%2 && 位置i的数是需要聚集的,则i位置就是最中间的数。可以二分判断前缀和大小来找i的位置,由于在不断求f(1 ~ n)的过程中,前缀和是变化的,所以用树状数组代替前缀和。复杂度n * log * log。
  最后的一步,求聚集的花费,假设左边有x个数,左边第i个数位置在pos[i],左边的第一个数聚集到中间:pos[mid] - pos[1] - (x-1) ,第二个:pos[mid] - pos[2] - (x-2)…所以总和起来就是 【x*pos[mid]】 - 【pos[1] + pos[2]…+ pos[x]】 - 【0~x-1】。第一个直接算,第二个树状数组维护位置和,第三个等差数列求和。

  code:

#include <bits\stdc++.h>
#define Lnnnb return 0;
#define ll long long
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define maxn 404040

using namespace std;
void eninit();
ll n,a[maxn],id[maxn],suf[maxn];
vector<ll>ans;

struct Tree_array{
    ll cmax = 400001 , c[maxn];
    void add(ll x,ll val){
        for(ll i = x ; i <= cmax ; i += (i&-i))
            c[i] += val;
    }
    ll query(ll x){
        ll res = 0;
        for(ll i = x ; i >= 1 ; i -= (i&-i))
            res += c[i];
        return res;
    }
}c,num,val;

void scan(){
    cin >> n ;
    for(ll i = 1 ; i <= n ; ++i)cin >> a[i] ;
}
ll binary(ll aim){
    ll l = 1 , r = n;
    while(l <= r){
        ll mid = (l+r)/2;
        if(num.query(mid) >= aim)r = mid-1;             ///空的位置要左移
        else l = mid+1;
    }
    return r+1;
}
ll coun(ll now , ll lmid , ll flagl , ll flagr){
    ll res = 0 , l = now/2-flagl , r = now/2-flagr;
    ///左边点的数量*中点位置 - 左边位置和 - (0~lmid-1)的和
    res += l * lmid;
    res -= val.query(lmid-1);
    res -= (1 + l)*(l)/2;
    ///右边
    res += val.query(n) - val.query(lmid);
    res -= r * lmid;
    res -= (1 + r)*(r)/2;
    return res;
}

void solve(){
    ll cos1 = 0 , cos2 = 0;
    for(ll i = n ; i >= 1 ; --i){
        id[ a[i] ] = i;
        suf[ a[i] ] = c.query(a[i]);
        cos1 += suf[a[i]];
        c.add(a[i] , 1);
        num.add(i , 1);                         ///num记录的是剩下的数
        val.add(i , i);                         ///val记录位置总和
    }
    for(ll i = n ; i >= 1 ; --i){
        ans.push_back(cos1 + cos2);

        cos2 = 0;
        num.add(id[i] , -1);                    ///去掉i
        val.add(id[i] , -id[i]);

        ll now = i-1;
        ll lmid = binary( (now)/2+now%2 );            ///找到剩下的数的左中点

        cos2 = coun(now,lmid,now%2==0,0);
        if(now%2 == 0)cos2 = min(cos2 , coun(now , binary(now/2+1) , 0, 1));

        cos1 -= suf[i];
    }
    reverse(ans.begin() , ans.end());
    for(ll i = 0 ; i < ans.size() ; ++i)cout << ans[i] << " " ;
}

int main(){
    ios::sync_with_stdio(false);   cin.tie(0);cout.tie(0);
    ll t = 1;
    ///cin >> t ;
    while(t--){
        scan();
        solve();
        eninit();
    }
    Lnnnb
}

void eninit(){

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值