NOIP提高组模拟赛6

A. 简单的区间

这题看着真熟啊,好像把之前的english,入阵曲杂糅了一下。

首先,像入阵曲一样计算出前缀和\(s[]\)式子可以转化为求

\(s[r]-s[l-1]\equiv MAX(mod\;k)\)

像english一样
用单调栈处理出以X为最大值的区间,分区间求解

每次枚举一侧区间,已知MAX,只要知道另一侧有多少与之余数相同的即可

然鹅这题数据范围不允许用前缀和优化某区间余数为特定值的个数,这样english中的启发式合并就没有意义,因为两边都要扫,如果数据相对单调,非常容易卡成\(O(N^2)\)喜提TLE。

所以揉了两道题做不出怎么办?再揉一道

像之前的数颜色那样用vector存余数为i的前缀和的下标,每次查询直接二分,这样复杂度就可以了,记得存一个0进去

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=1000005;
int a[MAXN],l[MAXN],r[MAXN],stc[MAXN],top,n,k;
long long sum[MAXN],ans=0;
vector<int>v[MAXN];
int ask(int ll,int rr,int y){
    int p1=lower_bound(v[y].begin(),v[y].end(),ll)-v[y].begin();
    int p2=upper_bound(v[y].begin(),v[y].end(),rr)-v[y].begin();
    return p2-p1;
}
void work(int ll,int rr,int xx){
    if(rr-xx<xx-ll)    for(int i=xx;i<=rr;++i)ans+=ask(ll-1,xx-1,(sum[i]-a[xx])%k);
            else         for(int i=ll;i<=xx;++i)ans+=ask(xx,rr,(sum[i-1]+a[xx])%k);
    --ans;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
    for(int i=1;i<=n;++i){
        while(top&&a[stc[top]]<=a[i]){r[stc[top]]=i-1;--top;}
        l[i]=stc[top]+1;
        stc[++top]=i;
    }
    while(top){r[stc[top]]=n;--top;}
    for(int i=0;i<=n;++i)v[sum[i]%k].push_back(i);
    for(int i=1;i<=n;++i)if(l[i]<r[i])work(l[i],r[i],i);
    printf("%lld\n",ans);
    return 0;
}

B. 简单的玄学

首先能够得到,至少有两个变量取值相同的概率,就是1-任意两个变量取值都不同的概率

\(1-2^n(2^n-1)(2^n-2).....(2^n-m+1)/(2^{nm})\)

如何计算这个式子,并且保留分数形式?

----约分

分母只有2这个因子,上下约分一定只能约2的几次方

如何约分?

有:\(2^n-x\)\(x\)的2的因子个数一样

性感感性理解下

\(x=2^p*q\)(q不含2这个因子)

\(2^n-x=2^p(2^{n-p}+q)\)

因为q不含2这个因子,所以q是奇数,\((2^{n-p}+q)\)也是奇数,不含2这个因子,所以\(2^n-x\)\(x\)的2的因子个数一样,即P

所以判断能约几个2,判断分母和\(m!\)有几个2这个因子即可

\(m!\)除以2,得到其中有几个含2这个因子的,除以4得到有几个含两个2这个因子的....不停除以2的此方,直到大于\(m!\)累计一下个数,得到约分的数,上下乘以逆元即可

#include<cstring>
#include<cstdio>
#include<cmath>

using namespace std;
const long long mod=1e6+3,nv=500002;
long long m,n;

long long qp(long long x,long long y){
    long long sum=x,ans=1;
    while(y){
        if(y&1)ans=(ans*sum)%mod;
        sum=(sum*sum)%mod;
        y>>=1;
    }
    return ans;
}

int main(){
    scanf("%lld%lld",&n,&m);
    if(log2(m)>n){printf("1 1\n");return 0;}
    long long p=qp(2,n),q=qp(p,m);
    long long a=1;
    for(int i=0;i<m;++i){a=a*(p-i)%mod;if(!a)break;}
    long long yf=n;--m;
    for(long long i=2;i<=m;i<<=1)yf+=m/i;
    long long inv=qp(nv,yf);
    q=(q*inv)%mod; a=(a*inv)%mod;
    printf("%lld %lld\n",(q-a+mod)%mod,q%mod);
    return 0;
}

C. 简单的填数

想出过卡上下界的做法,但是不知道怎么处理\(a[i]\)有数的情况,还是太菜

定义二元组,(其实就是两个数组)up[i] \((x,c)\) 和down[i] \((x,c)\),表示上下界,即到第i个数,最小是down->x,出现次数down->c 最大up-x,出现次数up->c

显然一般情况up每两个加一,down每5个加一,如果\(a[i]\)有数,先用上下界判是否合法,然后更新上下界,由于要让up最大,down最小,显然令\(up->x=a[i] \;up->c=2\;down->x=a[i]\;down->c=1\)

这样就能判断是否存在合法方案,并且求出最大值,(注意特判一下
\(up[n]->c==1\)需要减1)

然后考虑如何填数,倒叙填数,定义now和sum,sum表示连续填了几个相同的数,每次填\(min(up[i]->z,now)\);当\(a[i]\)有数,\(now=min(now,a[i])\);sum每到5 now要减一

不是很理解,先口胡一下,不对请大佬指正

这个now大概是个上界,而我们此时需要尽可能小(减的快),就需要用up此时是下界,但是由于正着扫时候\(a[i]\)有数是直接暴力修改,导致中间会断,部分的下界是错误的,需要用上界修正,而now的作用就在这时候体现了,就是说up和now交替合法

#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=200005;
int min(int x,int y){return x<y?x:y;}
int a[maxn],n;
struct eyz{
    int z,c;
}up[maxn],down[maxn];
int as[maxn];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    up[1].z=1;up[1].c=1;down[1].c=1;down[1].z=1;
    bool flag=0;
    for(int i=2;i<=n;++i){
        if(up[i-1].c>=2)  { up[i].z=up[i-1].z+1;   up[i].c=1; }
        else  {  up[i].z=up[i-1].z; up[i].c=up[i-1].c+1;}
        if(down[i-1].c<5){  down[i].c=down[i-1].c+1;  down[i].z=down[i-1].z;}
        else{ down[i].c=1; down[i].z=down[i-1].z+1;}
        if(a[i]){
            if(a[i]<down[i].z||a[i]>up[i].z){flag=1;break;}
            if(up[i].z>a[i]){up[i].z=a[i];up[i].c=2;}
            if(down[i].z<a[i]){down[i].z=a[i];down[i].c=1;}
        }
    }
    if(up[n].c==1){up[n].c=up[n-1].c+1;up[n].z=up[n-1].z;}
    if(flag||up[n].z<down[n].z){printf("-1\n");return 0;}
    int now=up[n].z,num=1;
    for(int i=n;i>=1;--i){
        if(a[i]&&now>a[i])now=a[i],num=0;
        if(num==5)--now,num=0;
        ++num;
        as[i]=min(now,up[i].z);
    }
    printf("%d\n",up[n].z);
    for(int i=1;i<=n;++i)printf("%d ",as[i]);
    return 0;
}
posted @ 2022-01-22 22:11  Chen_jr  阅读(39)  评论(0)    收藏  举报