专栏文章
题解:AT_arc101_b [ABC107D] Median of Medians
AT_arc101_b题解参与者 9已保存评论 9
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 9 条
- 当前快照
- 1 份
- 快照标识符
- @minrtval
- 此快照首次捕获于
- 2025/12/02 07:19 3 个月前
- 此快照最后确认于
- 2025/12/02 07:19 3 个月前
我敢打赌,这肯定是洛谷这道题里最详细的题解了。
思路:
写这道题的时候可以先去 P3031 看一下,这篇题解就以此为突破口。
对于 P3031 这道题,我们可以先打暴力。
P3031 の 分解法:
我们学过,如果想要找子串,我们可以枚举左端点和右端点,如下:
CPP for(int l=1;l<=n;l++){
for(int r=l;r<=n;r++){
/*这里写中位数判断*/
}
}
于是我们这个暴力只需要注意判断子串中位数是否大于等于 。
我们将中位数判断部分记为函数
check(l,r)。那么我们改怎么写 check(l,r) 呢?很明显,作为暴力,我们可以再用一个数组来存储 ,记为 ,长度为 。我们需要判断的便是 。中位数的定义如下:
将 按升序排序得到数列 。此时, 的第 个元素的值即为 的中位数。这里, 表示向下取整的除法。
定义来源于题目。
判断完 后,如果满足就加 ,对于每个子串都来一遍,输出即可。
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x;
int a[1000005];
int b[1000005];
int check(int l,int r){
int m=0;
for(int i=l;i<=r;i++){
b[++m]=a[i];
}
sort(b+1,b+m+1);
return (b[m/2+1]>=x?1:0);
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
cout.tie(0)->sync_with_stdio(0);
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int ans=0;
for(int l=1;l<=n;l++){
for(int r=l;r<=n;r++){
ans+=check(l,r);
}
}
cout<<ans;
return 0;
}
P3031 の 分解法:
通过暴力得到 分后,我们便要向 分进发。
我们可以发现,对于每一个字串,如果希望 ,那么就肯定满足这个性质:
- 设 为数组 中大于等于 的数字个数, 为数组 中小于 的数字个数,那么 一定满足 。
这个性质是怎么来的?我们可以想,排完序后,如果 满足,那么大于等于 的数字的数量一定大于等于 ,即 。而这个数,一定满足 。
这里也就间接的证明了 。因为由上定义可以得到 。这里的 在代码中就是
m/2,编译器会自动向下取整。最后便是如何计算 。这个问题很简单,我们记大于等于 的数字为 ,小于 的数字为 。进行前缀和后,记前缀和数组为 ,对于区间 ,答案便为 。
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[1000005],sum[1000005];
int n,m,k,x;
signed main(){
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>=x){
sum[i]=1;
}
else{
sum[i]=-1;
}
sum[i]+=sum[i-1];
}
int ans=0;
for(int l=1;l<=n;l++){
for(int r=l;r<=n;r++){
if(sum[r]-sum[l-1]>=0){
ans++;
}
}
}
cout<<ans;
return 0;
}
P3031 の 分解法:
拿下 分后, 分便迎刃而解。
我们观察一下 分代码的 ,调整一下得:
我们将其与 联立得:
似乎在哪里见过这个式子,我们将 替换为 , 替换为 ,便可得到:
这下完全想起来了,这不就是 P1908 逆序对的颠倒版吗!
我们知道逆序对是可以用树状数组来记录之前比 大得数字,当然也可以记录之后比 小的数字。而这里需要记录的是之前小于等于 的数字。为什么?原因就是上面的式子,我不信有理解不了的。
接下来就是处理正负数的问题。很明显,数字区间是 ,而树状数组处理不了小于等于 的数字,所以这里统一加上 ,这样子数字区间 了。
最后还要注意存入时,第一步时 ,这一个不能漏了,记得还要开
CPPlong long。#include<bits/stdc++.h>
#define int long long
using namespace std;
const int b=100001;
int a[1000005],sum[1000005];
int tr[1000005];
int n,m,k,x;
int ans[1000005];
int lowbit(int x){
return x&-x;
}
void modify(int x,int v){
while(x<=210001){
tr[x]+=v;
x+=lowbit(x);
}
}
int query(int x){
int ans=0;
while(x){
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
signed main(){
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>=x){
sum[i]=1;
}
else{
sum[i]=-1;
}
}
modify(0+b,1);
int ans=0;
for(int i=1;i<=n;i++){
sum[i]+=sum[i-1];
ans+=query(sum[i]+b);
modify(sum[i]+b,1);
}
cout<<ans;
return 0;
}
AT_arc101_b の 分解法:
讲了这么久的 P3031,是时候该讲 AT_arc101_b 了。
- 对于不同的 ,较大的 满足的概率绝对比较小的 的低。
于是我们可以以 为区间,每次判断的是 ,如果 符合,那么 ,反之 。
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
const int b=100001;
int a[1000005],c[1000005],sum[1000005];
int tr[1000005];
int n,m,k;
int ans[1000005];
int lowbit(int x){
return x&-x;
}
void modify(int x,int v){
while(x<=210001){
tr[x]+=v;
x+=lowbit(x);
}
}
int query(int x){
int ans=0;
while(x){
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
bool check(int x){
memset(tr,0,sizeof tr);
memset(sum,0,sizeof sum);
for(int i=1;i<=n;i++){
if(a[i]>=x){
sum[i]=1;
}
else{
sum[i]=-1;
}
}
modify(0+b,1);
int ans=0;
for(int i=1;i<=n;i++){
sum[i]+=sum[i-1];
ans+=query(sum[i]+b);
modify(sum[i]+b,1);
}
if(ans>=(n*(n+1)/2+1)/2) return 1;
return 0;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
c[i]=a[i];
}
sort(c+1,c+n+1);
int l=1,r=n;
while(l<r){
int mid=(l+r+1)/2;
if(check(c[mid])) l=mid;
else r=mid-1;
}
cout<<c[l];
return 0;
}
后话:
本篇题解编辑历时 小时,给个赞再走呗。
相关推荐
评论
共 9 条评论,欢迎与作者交流。
正在加载评论...