Codeforces Round #654 (Div. 2) E2 - Asterism (Hard Version)(思維題/區間滾動or雙指針or排序)

題目

給定一個n,p(2<=p<=n<=1e5),保證p是素數

以下一個長度爲n的數組a[],第i個數ai(1<=ai<=1e9),代表第i個人手中的糖果數

 

設Yuzu初始有x塊糖,以下Yuzu要參加一個比糖果多少的遊戲,

她可以預先指定這個比的順序,也就是1-n這n個人的排列,

如果Yuzu的糖大於等於對方的糖,Yuzu就贏了,

並且本輪中還會獲得一塊糖,然後再比下一輪

Yuzu想贏得所有的n輪遊戲,設能滿足其要求的n個人的排列數爲f(x)

若f(x)不是p的倍數,則稱x是"好"的,

求"好"的x的個數,並輸出所有的x

思路來源

1、2來自官方題解,3來自題解下方評論區及羣友

題解

1、2:

設mx=max{ai},也就是最多的糖果數

首先,注意到,(不要在意等號這些細節,可能會差1)

所需的糖數x不會少於mx-n,否則把mx放到最後一位也比不贏,

也就意味着所有的場次都比不贏, f(x)=0,%p==0

所需的糖數x不會多於mx,否則怎麼安排順序都會贏,f(x)=n!,%p==0

也就是,只需要在[mx-n,mx]這個長度爲n的區間檢查答案

 

記num[i]爲糖果數不超過i的人數,f(x)=\prod_{i=0}^{n-1}(num[x+i]-i)

對應了i從0到n-1時,當前糖果數爲x+i時,每次num[x+i]-i個人可以挑戰

f(x)不爲p整除,令g(i)=num[x+i]-i,即任意g(i)都不爲p整除,則g(i)取值應均在[1,p-1],

考察g函數的性質,

若固定x,前一項隨着i的增加而遞增(非嚴格),後一項減1,所以g(i)要麼遞增,要麼最多減1,

若對於某一個x,畫出g的函數圖像,發現x遞增的時候,函數圖像會向上平移(非嚴格),

即在x=x_{0}時的g(i),一定不超過x=x_{0}+1時的g(i),

所以,答案應該是連續的一段,從某個值函數圖像均落在[1,p-1]起,到往上平移,挪出這個區間

 

題解1:

令x的左界lb=mx-n+1,如果對於當前i,num[lb+i]-i<1,說明應該增加下界,使g(i)更大, 

不斷增lb,直至當前的i合法,再檢查下一個i

令x的右界ub=mx-n+n,如果對於當前i,num[lb+i]-i>=p,說明應該減小上界,使g(i)更小,

不斷減lb,直至當前的i合法,再檢查下一個i

題解2:

考慮f(x)=\prod_{i=0}^{n-1}(num[x+i]-i),以x+i代換i,有f(x)=\prod_{i=x}^{x+n-1}(num[i]-(i-x))=\prod_{i=x}^{x+n-1}(x-(i-num[i]))

即給定x,對於所有的i,都不存在i,滿足x\equiv (i-num[i])(mod\ p)

對於x=lb,其需要檢查[lb,lb+n-1]的i,

對於x=lb+1,其需要檢查[lb+1,lb+n]的i,

注意到只有左右界的單點修改,區間滾動

那麼,就預先把x=lb的對應的n個(i-num[i])mod\ p插入桶中,判斷x=lb是否合法,

再更改一下左右界,再檢查lb+1,依次類推,直至檢查完ub,

題解3:

先將a數組從小到大排序,則num[x+i]-i<p,即num[x+i]<p+i,排序後的數組中有num[a[p+i-1]]=p+i,

即恰滿足能戰勝p+i個的下界是x=a[p+i-1],說明num[x+i]<num[a[p+i-1]],有x+i<a[p+i-1],即x<a[p+1-i]-i

對於所有p+1-i合法的下標,檢查該不等式,取min即得上界ub

下界同理,由於num[x+i]-i>=1,有num[x+i]>=i+1=num[a[i]],即x+i>=a[i],x>=a[i]-i,

對於所有i合法的下標,檢查該不等式,取max即得下界lb

心得

一個題補了一天gg,發現性質很重要,然而着手怎麼做也很重要,

本題性質:num[x+i]-i>=1,num[x+i]-i<p,x只可能在[mx-n,mx]之間

代碼1

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=2e5+10;
int n,p,a[N],num[M],mx,lb,ub;
int main(){
    scanf("%d%d",&n,&p);
    for(int i=0;i<n;++i){
        scanf("%d",&a[i]);
        mx=max(mx,a[i]);
    }
    //mx-n<=x<=mx 偏移量挪動到[0,n]
    for(int i=0;i<n;++i){
        num[max(0,a[i]-(mx-n))]++;
    }
    for(int i=1;i<M;++i){
        num[i]+=num[i-1];
    }
    lb=1;
    //num[n+n] 所以用二倍下標
    for(int i=0;i<n;++i){
        while(num[lb+i]-i<1){
            lb++;
        }
    }
    ub=n;
    for(int i=0;i<n;++i){
        while(ub>0 && num[ub+i]-i>=p){
            ub--;
        }
    }
    if(lb>ub)puts("0");
    else{
        printf("%d\n",ub-lb+1);
        for(int i=lb;i<=ub;++i){
            printf("%d ",i+(mx-n));
        }
    }
    return 0;
}

代碼2

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=2e5+10;
int n,p,a[N],num[M],mx;
int res[N],c,now[N];
int f(int x,int y){
    return (x%y+y)%y;
}
int main(){
    scanf("%d%d",&n,&p);
    for(int i=0;i<n;++i){
        scanf("%d",&a[i]);
        mx=max(mx,a[i]);
    }
    //mx-n<=x<=mx 偏移量挪動到[0,n]
    for(int i=0;i<n;++i){
        num[max(0,a[i]-(mx-n))]++;
    }
    for(int i=1;i<M;++i){
        num[i]+=num[i-1];
    }
    for(int i=0;i<n;++i){
        now[f(i+(mx-n)-num[i],p)]++;
    }
    for(int i=0;i<=n;++i){
        if(now[f(i+(mx-n),p)]==0){
            res[++c]=i+(mx-n);
        }
        now[f(i+(mx-n)-num[i],p)]--;
        now[f((n+i)+(mx-n)-num[i+n],p)]++;
    }
    printf("%d\n",c);
    for(int i=1;i<=c;++i){
        printf("%d ",res[i]);
    }
    return 0;
}

代碼3

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,p,a[N],lb,ub;
int main(){
    scanf("%d%d",&n,&p);
    for(int i=0;i<n;++i){
        scanf("%d",&a[i]);
        ub=max(ub,a[i]);
    }
    sort(a,a+n);
    for(int i=0;i<n;++i){
        lb=max(lb,a[i]-i);
    }
    for(int i=0;p+i-1<n;++i){
        ub=min(ub,a[p+i-1]-i-1);
    }
    if(lb>ub)puts("0");
    else{
        printf("%d\n",ub-lb+1);
        for(int i=lb;i<=ub;++i){
            printf("%d ",i);
        }
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章