CF1852A Ntarsis' Set

题意

给定一个包含 $1$ 到 $10^{1000}$ 的集合 $S$。每次同时删除 $S$ 中的第 $a_1$ 个、第 $a_2$ 个、$\ldots$ 个、$a_n$ 个最小的数。

询问经过 $ k $ 天后,集合 $S$ 中最小的元素。

Solution

发现答案具有单调性,考虑二分答案。对于一个 $mid$,发现删除操作事实上是降低剩余数字的排名,故不断减去小于等于 $mid$ 的 $a_i$ 个数,即 $mid$ 在 $k$ 次删除中每次降低的排名。最后若排名大于 $0$,则当前 $mid$ 合法,寻找最小的合法的 $mid$ 即可。

注意特判 $a_1 \ne 1$ 的情况,此时答案一定为 $1$。

时间复杂度为 $\mathcal{O}(k\log nk)$,可以通过本题。

code

#include<bits/stdc++.h>
using namespace std;
inline long long read()
{
    long long res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
}
long long n,k;
long long val[200010];
bool check(long long mid)
{
    int point=n;
    for(int i=1;i<=k;i++)
    {
        while(val[point]>mid)
            point--;
        mid-=point;
    }
    return mid>0;
}
int main(int argc,const char *argv[])
{
    int T=read();
    while(T--)
    {
        n=read(),k=read();
        for(int i=1;i<=n;i++)
            val[i]=read();
        long long left=1,right=n*k+n,ans=n*k+n;
        if(val[1]!=1)
        {
            printf("1\n");
            continue;
        }
        while(left<right)
        {
            long long mid=(left+right+1)>>1;
            if(check(mid)==true)
                ans=mid,right=mid-1;
            else 
                left=mid;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

Solution

其实这道题有时间复杂度为 $\mathcal{O}(n+k)$ 的做法。

假设序列 $a$ 递增。考虑反向模拟,我们每次不删除操作中位置 $a_1,a_2,\cdots,a_n$ 的数字,然后再 check 第 $k$ 次操作后的第一个数字。从数字 $1$ 开始,再每次运算中,尽量在 $a_1-1, a_2-2, \cdots, a_{n}-n$ 后插入 $0$,使 $0$ 在插入结束后占据位置 $a_1, a_2, \cdots, a_n$。在插入 $k$ 次后,$1$ 所在的位置即为答案。

定义 $1$ 的位置为 $now$,在具体实现中,我们记 $cnt$ 为 $now$ 之前有多少个 $a_1-1,a_{2}-2,\cdots,a_{n}-n$,则每个插入可以以 $\mathcal{O}(1)$ 的时间复杂度处理。即如果 $a_1-1$ 到 $a_i-i$ 都在 $x$之前,那么在 $now$ 之前插入 $i$ 个零。

code

#include<bits/stdc++.h>
using namespace std;
inline long long read()
{
    long long res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
}
long long val[200010];
int main(int argc,const char *argv[])
{
    int T=read();
    while(T--)
    {
        long long n=read(),k=read();
        for(int i=1;i<=n;i++)
            val[i]=read();
        if(val[1]!=1)
        {
            printf("1\n");
            continue;
        }
        long long cnt=1,now=1;
        for(int i=1;i<=k;i++)
        {
            while(cnt<=n&&val[cnt]<=now+cnt-1)
                cnt++;
            now+=cnt-1;
        }
        printf("%lld\n",now);
    }
    return 0;
}
posted @ 2023-08-06 17:34  Che_001  阅读(15)  评论(0)    收藏  举报  来源