「题解」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;
}