「题解」P10235 [yLCPC2024] C. 舞萌基本练习

P10235 舞萌基本练习 题解

思路

看到最大值最小首先考虑二分答案

由于答案满足单调性,可以二分不优美度的最大值,也就是逆序对数的最大值。

我们在每次增加一个元素的时候都要求解当前区间的逆序对数,所以不能用归并排序求逆序对数,考虑树状数组解法。

如果不会树状数组求逆序对,请出门右转P1908 逆序对

具体解法都写在代码里了。

#include<bits/stdc++.h>
using namespace std;
const int MX=100100;
#define int long long   //不开long long 见祖宗
int n,k;
int a[MX]={0};
int mid[MX]={0};
void li(){                    //离散化
    for(int i=1;i<=n;i++)  mid[i]=a[i];
    sort(mid+1,mid+n+1);
    int len=unique(mid+1,mid+1+n)-mid-1;
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(mid+1,mid+1+len,a[i])-mid;
    }
}


int tree[MX]={0};                         //树状数组
inline int lowbit(int x){return x&(-x);}
void add(int x,int val){              
    for(;x<=n;x+=lowbit(x)){
        tree[x]+=val;
    }
}
int query(int x){
    int sum=0;
    for(;x;x-=lowbit(x)){
        sum+=tree[x];
    }
    return sum;
}

queue<int> qu;
void del(){                            //清空数组
    while(!qu.empty()){
        int x=qu.front();qu.pop();
        add(x,-1);
    }
}
bool check(int m){
    int l=1,r=1;           //l为当前区间左端点,r为当前区间右端点
    int sum=1,ni=0;        //sum为当前区间个数,ni为当前区间逆序对数
    while(r<=n){
        add(a[r],1);
        qu.push(a[r]);
        ni+=(r-l+1)-query(a[r]);      //统计逆序对数
        if(ni>m){                     //超过m就新划分一个区间
            sum++,l=r,ni=0;
            del();                    //树状数组清空
            if(sum>k)  break;         //如果超过k个就不合法
            continue;
        }
        r++;
    }
    del();    //记得清空数组
    if(sum>k)  return 0;              //如果超过k个就不合法
    return 1;
}
signed main(){
    int T;scanf("%lld",&T);
    while(T--){
        scanf("%lld%lld",&n,&k);
        for(int i=1;i<=n;i++) scanf("%lld",a+i);
        li();
        int l=0,r=n*n,mid,ans=n*n;
        while (l<=r)             //二分答案
        {
            mid=(l+r)>>1;
            if(check(mid))  r=mid-1,ans=mid;
            else  l=mid+1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2024-03-27 21:09  是菜菜呀  阅读(29)  评论(0编辑  收藏  举报