Codeforces 1371E Asterism

题目大意

\(n\) 个敌人,编号为 \(1\sim n\),第 \(i\) 个敌人有 \(a_i\) 个糖果。Yuzu在最开始时有 \(x\) 个糖果。当Yuzu拥有的糖果数大于等于她此时面对的敌人的糖果数时,它可以击败这个敌人,并取得1个糖果,否则她将被敌人击败,并且什么也得不到。Yuzu希望对所有的敌人都取得胜利,请帮她重新安排 \(n\) 个敌人的出现顺序,即 \(1\sim n\) 的一个合法的排列 \(P\)。让我们定义 \(f(x)\) 等于初始时Yuzu有 \(x\) 个糖果时这样的排列 \(P\) 的数量。给出 \(n,p\),其中 \(p\) 是质数,并且 \(p\leq n\)。 我们称 \(x\) 是好的,当且仅当 \(f(x)\) 不能被 \(p\) 整除。找到所有好的 \(x\)

此题分为 Easy 和 Hard 两个版本。
Easy 版本 \(2\leq p\leq n\leq 2000,1\leq a_i\leq 2000\)
Hard 版本 \(2\leq p\leq n\leq 10^5,1\leq a_i\leq 10^9\)

题解

首先我们考虑 \(x\) 的范围。
因为要面对每个敌人都取得胜利,那么由于每胜利一次就获得一个糖果,则面对最后一个敌人时Yuzu所具有的糖果数是 \(x+n-1\)。那么显然,一定有 \(x+n-1\geq max\{a_i\}\)。当 \(max\{a_i\}\leq x\),则说明 \(x\) 一开始就比所有的 \(a_i\) 大,随便怎么排都是合法的,排列数量是 \(n!\),但是 \(p\leq n\),则 \(p|n!\),这样的 \(x\) 都是不好的。 综上,有\(max\{a_i\}-n+1\leq x\leq max\{a_i\}\),即可能是好的 \(x\) 最多只有 \(n\) 个。

然后我们考虑如何去计算 \(f(x)\)
由于每击败一个敌人获得一个糖果,Yuzu面对第 \(i\) 个敌人时所具有的糖果数实际上是 \(x+i-1\),以 \(i\) 为横坐标,Yuzu的糖果数为纵坐标,这实际上是一条从 \((1,x)\)\((n,x+n-1)\) 的线段,是一个直角梯形的形状。要击败每一个敌人,即在排列 \(P\) 中,每个位置上的敌人的糖果数必须都在这条线段下方。那么贪心地想,我们首先考虑把糖果数最多的敌人放到这条线段下方,先把所有\(a_i\)从小到大排序,那么把糖果数最多的敌人放到这条线段下方的方案数是 \(x+n-1-a_n+1=x+n-a_n\),然后把糖果数次多的敌人放到这条线段的下方,方案数是\(x+n-1-a_{n-1}\)。如此继续下去,当 \(a_i\leq x\) 时,在剩下的位置随便怎么排都可以。然后根据乘法原理,即可得出答案。对于每一个 \(x\),我们可以很容易地在 \(O(n)\) 时间内求得 \(f(x)\),然后判断 \(f(x)\) 是否被 \(p\) 整除。由于 \(x\) 最多只有 \(n\) 个,这样做的时间复杂度是 \(O(n^2)\),可以通过 Easy 版本。

注意到按我们之前的从大到小的放置方法(\(a_i\) 仍先从小到大排序),当 \(a_i\leq x\) 时,在剩下的位置随便怎么排都可以,不妨设 \(pos\) 是使得 \(a_{pos}\leq x\) 的最大的数,那么把 \(a_1\sim a_{pos}\) 放到线段下方的方案数是 \(pos!\),因此当 \(pos\geq p\) 时,一定有 \(p|pos!\),这种 \(x\) 一定是不好的。所以对于每一个 \(x\),我们可以先二分找到这样的 \(pos\),然后判断是否有 \(pos\geq p\)。否则,注意到 \(p\) 是质数,由于\(p\nmid pos!\),若 \(x\) 是不好的,必有 \(p\) 整除把 \(a_{pos+1}\sim a_{n}\) 放到线段下方的方案数。注意到方案数实际上是\(x+n-a_n\)\(x+n-1-a_{n-1}\),即 \((x+n)-(a_{n-i}+i)\) 相乘的形式,我们只需判断是否存在一个 \((x+n)-(a_{n-i}+i)\) ,它能被 \(p\) 整除即可。那么必然有 \(x+n\equiv a_{n-i}+i (\mod p)\)。注意到对于特定的 \(x\)\((x+n) \mod p=r\) 是常数,我们只需判断是否存在 \((a_{n-i}+i) \mod p=r\)。我们可以预处理出 \((a_{n-i}+i)\mod p\) ,令\(Suf[m]\) 表示使得模数为 \((a_{n-i}+i)\mod p=m\) 的最大的 \(n-i\),那么只要有 \(Suf[r]> pos\),就说明存在 \(x+n\equiv a_{n-i}+i (\mod p)\),即 \(p|f(x)\)\(x\) 不是好的数。对于每一个 \(x\),这一判断可以 \(O(1)\) 完成,再加上之前二分的 \(O(\log n)\),总的时间复杂度是 \(O(n\log n)\),可以通过 Hard 版本。

Code (Easy版本)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;

#define RG register int
#define LL long long

template<typename elemType>
inline void Read(elemType &T){
    elemType X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    T=(w?-X:X);
}

int n,p;
int Data[2005];
vector<int> Ans;

int Calc(int x){
    int Res=1LL,num=0;
    int L=x,R=x+n-1;
    for(RG i=n;i>=1;--i){
        if(Data[i]<=L){
            if(n-num<=0) Res=0;
            else Res=Res*(n-num)%p;
        }else{
            if(R-Data[i]+1-num<=0) Res=0;
            else Res=Res*(R-Data[i]+1-num)%p;
        }
        ++num;
    }
    return Res;
}

int main(){
    Read(n);Read(p);
    for(RG i=1;i<=n;++i)
        Read(Data[i]);
    sort(Data+1,Data+n+1);
    for(RG x=max(1,Data[n]-n+1);x<Data[n];++x){
        if(Calc(x)) Ans.push_back(x);
    }
    int len=Ans.size();
    printf("%d\n",len);
    for(RG i=0;i<len;++i){
        printf("%d",Ans[i]);
        if(i<len-1) printf(" ");
    }
    printf("\n");
    return 0;
}

Code (Hard 版本)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;

#define RG register int
#define LL long long

template<typename elemType>
inline void Read(elemType &T){
    elemType X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    T=(w?-X:X);
}

int n,p;
int Data[100005],DataB[100005],Suf[100005];
vector<int> Ans;

int Calc(int x){
    int Res=1LL,num=0;
    int L=x,R=x+n-1;
    int m=(R+1)%p;
    int pos=upper_bound(Data+1,Data+n+1,L)-Data-1;
    if(pos>=p) return 0;
    if(Suf[m]>pos) return 0;
    return 1;
}

int main(){
    Read(n);Read(p);
    for(RG i=1;i<=n;++i)
        Read(Data[i]);
    sort(Data+1,Data+n+1);
    for(RG i=n;i>=1;--i){
        DataB[i]=(Data[i]+n-i)%p;
        Suf[DataB[i]]=max(Suf[DataB[i]],i);
    }
    for(RG x=max(1,Data[n]-n+1);x<Data[n];++x){
        if(Calc(x)) Ans.push_back(x);
    }
    int len=Ans.size();
    printf("%d\n",len);
    for(RG i=0;i<len;++i){
        printf("%d",Ans[i]);
        if(i<len-1) printf(" ");
    }
    printf("\n");
    return 0;
}
posted @ 2020-07-02 09:50  AE酱  阅读(313)  评论(1编辑  收藏  举报