• 20210712考试-2021noip11


    这篇总结比我写的好多了建议直接去看


    T1 简单的序列

    考场:
    愣了一会,想到以最大值分治。每次枚举最大值两侧更小的区间,st表预处理前缀和和最大值,用桶统计答案.

    注意分治时要去掉最大值。

    const int N = 3e5+5, X = 1e6+5;
    int n,k,a[N];
    
    int m,s[N],head[N],nxt[N*36],val[N*36],op[N*36],cnt[X];
    LL ans;
    
    namespace st {
    int lg[N],pw[19],f[19][N],p[19][N];
    void init() {
        lg[1] = 0;
        For(i,2,n) lg[i] = lg[i>>1]+1;
        pw[0] = 1;
        For(i,1,18) pw[i] = pw[i-1]<<1;
        For(i,1,n) f[0][i] = a[i], p[0][i] = i;
        For(j,1,18) for(int i = 1; i+pw[j]-1 <= n; ++i) {
            int mid = i+pw[j-1];
            if( f[j-1][i] > f[j-1][mid] )
                f[j][i] = f[j-1][i], p[j][i] = p[j-1][i];
            else f[j][i] = f[j-1][mid], p[j][i] = p[j-1][mid];
        }
    }
    int maxx(int l,int r) {
        int k = lg[r-l+1], mid = r-pw[k]+1;
        return f[k][l]>f[k][mid] ? p[k][l] : p[k][mid];
    }
    }
    
    void add(int x,int l,int r) {
        x %= k;
        if( x < 0 ) x += k;
    //    printf(">%d %d %d
    ",x,l,r);
        if( l ) val[++m] = x, op[m] = -1, nxt[m] = head[l-1], head[l-1] = m;
        val[++m] = x, op[m] = 1, nxt[m] = head[r], head[r] = m;
    }
    void solve(int l,int r) {
        if( l >= r ) { ans += l==r; return; }
        int p = st::maxx(l,r);
    //    printf(">%d %d %d
    ",l,p,r);
        if( p-l < r-p ) For(i,l,p) add(s[i-1]+a[p],p,r);
        else For(i,p,r) add(s[i]-a[p],l-1,p-1);
        solve(l,p-1), solve(p+1,r);
    }
    
    signed main() {
        read(n,k);
        For(i,1,n) read(a[i]), s[i] = (s[i-1] + a[i]) %k;
        st::init();
        solve(1,n);
        For(i,0,n) {
            ++cnt[s[i]];
            for(int j = head[i]; j; j = nxt[j]) ans += op[j] * cnt[val[j]];
        }
        printf("%lld",ans-n);
        return 0;
    }

    上界 $O(nlog(n))$

    或者传统的以 $mid$ ,严格 $O(nlog(n))$

    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N=300010,M=1100000;
    int t[M][2],a[N],s[N],mx[N],pos[N],n,k,H;
    long long ans;
    inline void read(int &e)
    {
        e=0;int w=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
        while(isdigit(ch)){e=e*10+ch-'0';ch=getchar();}
        e*=w;return;
    }
    inline void fenzhi(int l,int r)
    {
        if(l==r)
            return;
        int mid=(l+r)>>1;
        s[mid]=H=mx[0]=0;
        for(int i=mid+1;i<=r;i++)
        {
            if(a[i]>a[mx[H]])
                mx[++H]=i;
            s[i]=((s[i-1]+a[i])%k+k)%k;
            t[((s[i]-a[mx[H]])%k+k)%k][0]++;
            pos[i]=mx[H];
        }
        mx[H+1]=r+1;
        int p = 1,z = 0,p1 = mid + 1,mx1 = 0;
        for(int i=mid;i>=l;i--)
        {
            z=(z+a[i])%k,mx1=max(mx1,a[i]);
            while(p<=H&&a[mx[p]]<=mx1)
                ++p;
            while(p1<mx[p])
            {
                t[(s[p1]-a[pos[p1]]%k+k)%k][0]--;
                t[s[p1]][1]++;
                p1++;
            }
            ans+=t[(k+mx1%k-z)%k][1];
            if(p<=H)ans+=t[(k-z)%k][0];
        }
        for(int i=mid+1;i<p1;++i)
            t[s[i]][1]--;
        for(int i=p1;i<=r;++i)
            t[(s[i]-a[pos[i]]%k+k)%k][0]--;
        fenzhi(l,mid); 
        fenzhi(mid + 1,r);
    }
    int main ()
    {
        read(n);read(k);
        for(int i = 1;i <= n; ++i)
        {
            read(a[i]);
        }
        fenzhi(1,n);
        printf("%lld
    ",ans);
        return 0;
    }

     T2 简单的玄学

    显然有:

    $egin{align}ans=frac{prod_{i=n-m+1}^{n-1}2^n-i}{2^{n(m-1)}}\end{align}$

    考场:不难发现连乘部分是两端阶乘之商,而 $m$ 范围较大,阶乘中极易“经过”模数 $1000003$ 的倍数,那么只需要预处理至模数的阶乘就可以,对作为除数的阶乘求逆元。

    但如果作为阶乘取模变为0,但是两者仍然要约分怎么办,可以先取模再约分吗?如果先约分再取模是不是太了,应该是先取模再约分,举了个例子没有发现问题。

    于是炸了。

    题解:先取模再约分的话就太了,所以先约分再取模。

    显然有 $2^n-i$ 中2的个数等于 $i$ 中2的个数。

    故对上面的式子分母两次快速幂 $egin{align}a^{b} mod s=a^{b mod (s-1)} mod send{align}$ ,之后统计分子中2的因数个数,相当于统计 $(m-1)!$ 阶乘中2的因数个数:核心代码:

    long long ercnt=n;
        for(long long i=1;(1LL<<i)<m;i++)
        {
            ercnt+=(m-1)/(1LL<<i);
        }

    约分后取模,可以取模后分子分母同乘2的逆元的因数个数幂。切记不要想当然除gcd。

    #include<bits/stdc++.h>
    using namespace std;
    const int mod=1e6+3,phi=1e6+2;
    long long jc[mod+100],a,b,n,m,ercnt;
    long long quick_pow(long long x,long long y)
    {
        long long rec=1;
        while(y)
        {
            if(y&1)
            {
                rec=rec*x%mod;
            }
            y>>=1;
            x=x*x%mod;
        }
        return rec;
    }
    int main()
    {
        scanf("%lld%lld",&n,&m);
        long long ercnt=n;
        for(long long i=1;(1LL<<i)<m;i++)
        {
            ercnt+=(m-1)/(1LL<<i);
        }
        long long maxn=quick_pow(2,n%phi);
        b=quick_pow(maxn,m%phi);
        a=1;
        for(long long i=0;i<m;i++)
        {
            a=a*(maxn-i)%mod;
            if(!a)
                break;
        }
        b=b*quick_pow(500002,ercnt)%mod;//2逆元500002
        a=a*quick_pow(500002,ercnt)%mod;
        printf("%lld %lld
    ",(b-a+mod)%mod,b);
        return 0;
    }

    T3 简单的填充

    考场:没看

    DP,维护 $up$ 和 $down$,分别表示当前格子最大可能能放的数字以及这个数字是第几次重复,和当前格子最小可能能放的数字以及这个数字是第几次重复。遍历时两者都继承上一个格子,上限每重复两次就更新,下限则是五次。如果当前格子有值就用它纠正二者,矛盾则impossible。为满足题意,最后剩下的格子(与 $n$ 点间没有确定的数)尽量多放大数。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1100000;
    int upc[N],upx[N],a[N],n,downc[N],downx[N],tong[N]; 
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        if(a[1]!=1&&a[1]!=0)
        {
            puts("-1");
            return 0;
        }
        upc[1]=1;upx[1]=1;
        downc[1]=1;downx[1]=1;
        for(int i=2;i<=n;i++)
        {
            upc[i]=upc[i-1];upx[i]=upx[i-1];
            downc[i]=downc[i-1];downx[i]=downx[i-1];
            if(++upc[i]>2)
                upc[i]=1,upx[i]++;
            if(++downc[i]>5)
                downc[i]=1,downx[i]++;
            if(a[i])
            {
                if(upx[i]>a[i])
                {
                    upx[i]=a[i];
                    upc[i]=2;
                }
                else if(upx[i]==a[i])
                {
                    upc[i]=min(upc[i],2);
                }
                if(downx[i]<a[i])
                {
                    downx[i]=a[i];
                    downc[i]=1;
                }
                if(upx[i]<a[i]||downx[i]>a[i])
                {
                    puts("-1");
                    return 0;
                }
            }
        }
        if(upc[n]==1)
        {
            upx[n]=upx[n-1];
        }
        if(upx[n]<downx[n])
        {
            puts("-1");
            return 0;
        }
        printf("%d
    ",upx[n]);
        tong[upx[n]]++;
        a[n]=upx[n];
        for(int i=n-1;i>=1;i--)
        {
            if(!a[i])
            {
                int tmp=min(a[i+1],upx[i]);
                if(tong[tmp]==5)
                    tmp--;
                a[i]=tmp;
            }
            tong[a[i]]++;
        }
        for(int i=1;i<=n;i++)
        {
            printf("%d ",a[i]);
        }
        return 0;
    }

    T1&T2都几乎正解思路但是打成不如暴力分QWQ

  • 相关阅读:
    iOS开发之字符串去掉首尾空格换行
    iOS开发之截取UIScrollView长图方法、截长图
    iOS开发之语音录制
    iOS开发之程序各种状态监听
    iOS开发之监听应用进入前台后台
    iOS开发之波浪动画效果
    mysql 主从一致性检查
    git 备份和恢复
    tomcat server.xml配置文件 解析
    检查MySQL主从数据一致性
  • 原文地址:https://www.cnblogs.com/HKHbest/p/15003338.html
Copyright © 2020-2023  润新知