Logo zrl123456 的博客

博客

T316879 挑战赛 讲解

...
zrl123456
2025-12-01 12:51:25
我咋啥也不会/ll

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-02-02 13:10:42

题目
本题考察内容: 排序, 贪心, 二分, 前缀。

40~60 分做法

首先我们先将初始成绩降序排序, 方便运行。
要使第 $i$ 位选手获得第一名, 那么就要让其获得 $n$ 分, 让第 $1$,$2$,$\dots$,$i-1$ 位选手获得 $1$,$2$,$\dots$,$i-1$ 分, 让第 $i+1$,$i+2$,$\dots$,$n$ 位选手获得 $i$,$i+1$,$\dots$,$n-1$ 分。
如果第 $i$ 位选手是第一名, 那么他就能当第一名; 否则他就不能当第一名。
上面就是我们的贪心思想。
时间复杂度为 $O(n^2)$, 无法通过 $n\le 3\times 10^5$ 的数据。

100 分做法一

我们发现, 在循环内我们做了许多无用的事情, 我们可以提前处理好一个前缀最值, 后面直接调用即可。
总体时间复杂度为 $O(n)$(撇开排序), 可以通过。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register\/\/一点常数优化
using namespace std;
const int N=3e5+5; 
int a[N],n,maxx,sum;
inl int read(){\/\/快读函数, 可以直接用 cin 代替
	reg int f=1,x=0;
	reg char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){\/\/快写, 可用 cout 代替
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
	return;
}
signed main(){
    n=read();
    for(reg int i=1;i<=n;++i) a[i]=read();
    sort(a+1,a+1+n,greater<int>());\/\/降序排序
    for(reg int i=1;i<=n;++i) maxx=max(maxx,a[i]+i);\/\/前缀最值
    for(reg int i=1;i<=n;++i)
        if(a[i]+n>=maxx) sum++;\/\/一重循环查找
    write(sum);
    return 0;
}

100 分做法二

我们发现, 如果第 $i$ 位选手能获得第一名, 那么第 $i-1$,$i-2$,$\dots$,$1$ 位选手都可以获得第一名; 如果第 $i$ 位选手不能获得第一名, 那么第 $i+1$,$i+2$,$\dots$,$n$ 为选手也不能获得第一名。
可以发现, 它具有单调性!
这说明, 此题可以使用二分。
时间复杂度为 $O(n\log_2n)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define N 300005
#define INF 2147483647
using namespace std;
int n,a[N],b[N];
inl int read(){
    reg int f=1,x=0;
    reg char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return f*x;
}
inl void write(int x){
    if(x<0) x=-x;
    if(x>=10) write(x\/10);
    putchar(x%10^48);
}
inl bool check(int x){
    reg int tot=n-1;
    for(reg int i=1;i<=n;++i,--tot)
        if(b[i]!=a[x]&&(b[i]+tot>a[x]+n||b[i]+tot==a[x]+n&&i<x)) return false;\/\/这个判断条件可能有点难理解
    return true;
}
inl int lower(){\/\/二分模板
    reg int l=1,r=n,mid=0,ans=0;
    while(l<=r){
        mid=l+r>>1;\/\/位运算
        if(check(mid)){
            ans=mid;
            l=mid+1;
        }else r=mid-1;
    }
    return ans;
}
signed main(){
    n=read();
    for(reg int i=1;i<=n;++i) a[i]=read();
    memcpy(b,a,sizeof(a));\/\/复制数组,一个降序排,一个升序排
    sort(a+1,a+n+1,greater<int>());
    sort(b+1,b+n+1);
    write(lower());\/\/查找到的答案即此题答案
    return 0;
}

100 分做法三

你可以发现, 这两种方法是可以组合在一起的!
这样的话, 查找的时间复杂度仅为 $O(\log_2n)$!
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define N 300005
#define INF 2147483647
using namespace std;
int n,a[N],maxx;
inl int read(){
    reg int f=1,x=0;
    reg char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return f*x;
}
inl void write(int x){
    if(x<0) x=-x;
    if(x>=10) write(x\/10);
    putchar(x%10^48);
}
int lower(){
    int l=1,r=n,mid=0,ans=0;
    while(l<=r){
        mid=l+r>>1;
        if(a[mid]+n>=maxx){
            ans=mid;
            l=mid+1;
        }else r=mid-1;
    }
    return ans;
}
signed main(){
    n=read();
    for(int i=1;i<=n;++i) a[i]=read();
    sort(a+1,a+n+1,greater<int>());
    for(int i=1;i<=n;++i) maxx=max(maxx,a[i]+i);\/\/两种方法组合在一起,不解释
    write(lower());
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。