• NOIP模拟测试「简单的区间·简单的玄学·简单的填数·简单的序列」


    简单的区间

    $update$

    终于$AC$了

    找到$(sum[r]+sum[l](sum表示以中间点为基准的sum)-mx)\%k==0$的点

    注意这里$sum$表示是以$mid$为基准点,(即$sum[l]$为后缀和,$sum[r]$为前缀和)

    回忆$(sum[r]-sum[l])\%k==0$这个经典问题做法(入阵曲简化版),开桶,桶里维护$sum[l]\%k$,那么$r$贡献就是桶里$sum[r]\%k$个数

    于是这个题开桶维护$sum$,问题转化为求$max$即可

    记录$max$位置是否$>mid$,区别对待

    设$f[i][0]$表示$max$在$mid$右面,$f[i][1]$表示$max$在$mid$左面

    $f[i][0]$存下右面$sum[r]-mx$,找桶里是否存在左面$sum[l]$
    $f[i][1]$存下右面$sum[r]$ 找到左面是否存在$mx-sum[l]$
    完了

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long 
    #define A 1010101
    ll f[A][2],a[A],pos[A],mx[A],sum[A];
    ll cnt,n,k,ans;
    void solve(ll l,ll r){
        if(l==r) return ;
        ll mid=(l+r)>>1;
        cnt=sum[mid]=mx[0]=0;
        for(ll i=mid+1;i<=r;i++){
            if(a[i]>a[mx[cnt]]) mx[++cnt]=i;
            sum[i]=(sum[i-1]+a[i])%k;
            f[(sum[i]-a[mx[cnt]]%k+k)%k][0]++;
            pos[i]=mx[cnt];
    //        printf("f[%lld]=%lld  sum=%lld cnt=%lld
    ",(sum[i]-a[mx[cnt]]%k+k)%k,f[(sum[i]-a[mx[cnt]]%k+k)%k][0],sum[i],cnt);
        }
        mx[cnt+1]=r+1;
        ll suml=0,rnow=mid+1,mxl=0,p=1;
        for(ll i=mid;i>=l;i--){
            suml=(suml+a[i])%k;
            mxl=max(mxl,a[i]);
            while(p<=cnt&&a[mx[p]]<=mxl) p++;
            while(rnow<mx[p]) {
                f[(sum[rnow]-a[pos[rnow]]%k+k)%k][0]--;
                f[sum[rnow]%k][1]++;
                rnow++;
            }
    //        printf("ans=%lld f[%lld][1]=%lld p=%lld rnow=%lld mx[%lld]=%lldsum[%lld]=%lld
    ",ans,(k+mxl%k-suml)%k,f[(k+mxl%k-suml)%k][1],p,rnow,p,mx[p],rnow,sum[rnow]);
            ans+=f[(mxl-suml+k)%k][1];
            if(p<=cnt) ans+=f[(k-suml)%k][0];
    //        printf("ans=%lld f[%lld][0]=%lld
    ",ans,k-suml,f[(k-suml)%k][0]);
        }
        for(ll i=mid+1;i<rnow;i++)
            f[sum[i]][1]--;
        for(ll i=rnow;i<=r;i++)
            f[(sum[i]-a[pos[i]]%k+k)%k][0]--;
        solve(l,mid);solve(mid+1,r);
    }
    int main(){
        scanf("%lld%lld",&n,&k);
        for(ll i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        solve(1,n);
        printf("%lld",ans);
    }
    View Code

    简单的玄学

    题解

    题目中说至少两个相同那么答案就所有方案-全不相同

    所有方案${(2^n)}^m=2^{n*m}$,

    互不相同,首先第一个随便选剩下避开已经选过就行$2^n*2^{n-1}......2^{n-m+1}$

    那么题目很傻逼的让你取模并且约分,你需要先约分再取模(取模再约分的话这个题就太水了,所以是先约分再取模)

    思考怎么约分

    下面全是$2$多少次方,于是我们看上面多少个二就行了

    $2^n*(2^{n}-1)......({2^n}-m+1)$很恶心,思考转化

    性质:$2^n-x$中二个数$=$$x$中二的个数

    证明:假设$x$可以表示为$j*(2^w)$(j可以是分数),乘法分配率$2^w*(2^{n-w}-j)$后面这个里面没有别的$2$因子了,原式$=$$2^w$,又$j$中没有$2$因子故相乘因子数不变,得证

    那么原式就变成求$(m-1)!$里$2$因子数

    可以简单求

        for(ll i=1;(1ll<<i)<=m-1;i++){
            (ercnt+=(m-1)/(1ll<<i));
        }

    例如$1$ $2$ $3$ $4$ $5$ $6$ $7$ $8$这个序列

    分别有$(2^1)*2$,$(2^2)*1$,$(2^3)*1$那么就是$8/8+8/4+8/2$

    可以看作$/2$时给所有有$2$因子填上一个二(即$2$,$4$,$6$,$8$中填一个2),此时$4$还剩$1$个没填$8$还剩$2$个没填

    $/4$给$4$,$8$里填此时$8$还剩$1$个没填

    最后$/8$,全部填满

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const ll mod=1e6+3;
    const ll phi=1e6+2;
    ll x,y,n,m;
    ll meng(ll x,ll k){
        ll ans=1;
        for(;k;k>>=1,x=x*x%mod)
            if(k&1)
                ans=ans*x%mod;
        return ans;
    }
    ll gcd(ll x,ll y){
        if(y==0) return x;
        return gcd(y,x%y);
    }
    int main(){
    //    freopen("sd.txt","w",stdout);
        scanf("%lld%lld",&n,&m);
    /*    if(log(m)>n){
            printf("1 1
    ");
            return 0;
        }
    */    ll maxn=meng(2,n%phi);
        y=meng(maxn,m%phi);
        x=1;
        ll ercnt=n;    
        for(ll i=0;i<m;i++){
    //        printf("maxn-i=%lld i=%lld m=%lld x=%lld
    ",maxn-i,i,m,x);
            x=x*(maxn-i)%mod;
            if(!x) break;
        }//2逆元500002
        for(ll i=1;(1ll<<i)<=m-1;i++){
            (ercnt+=(m-1)/(1ll<<i));
            printf("ercnt=%lld 1<<=%lld
    ",ercnt-n,1ll<<i);
        }
        y=y*meng(500002,ercnt)%mod;
        x=x*meng(500002,ercnt)%mod;
        printf("%lld %lld
    ",(y-x+mod)%mod,y);
    }
    View Code

    简单的填数

    题解

    一个$up$代表填的上界,$down$代表填的下界

    先不考虑已经填了的

    $up$两位一进,$down$五位一进

    考虑已经填的

    先考虑上界

    若$a[i]>up$比上界大肯定不合法

    若$a[i]=up$取$min(2,up)$

    若$a[i]<up$则将$up$调整到$a[i]$次数变为$2$

    下界类似

    若$a[i]<down$比下界小不合法

    若$a[i]>down$将$down$调整到$a[i]$

    统计答案时反着扫

    序列为什么不是$up$呢

    7
    0 0 0 2 0 2 0 
    正解
    2
    1 1 2 2 2 2 2 
    用up:
    2
    1 1 2 2 3 2 2 

    代码

    /*
    7
    0 0 0 2 0 2 0 
    hack
    2
    1 1 2 2 2 2 2 
    up:
    2
    1 1 2 2 3 2 2 
    10f
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define A 1010101
    struct node {
        ll cnt,x;
    }up[A],down[A];
    ll n;
    ll a[A],tong[A];
    int main(){
    //    freopen("da.in","r",stdin); freopen("ans.bf","w",stdout);
        scanf("%lld",&n);
        for(ll i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        if(a[1]!=1&&a[1]!=0){
            printf("-1
    ");
            return 0;
        }
        up[1].cnt=1,up[1].x=1;
        down[1].cnt=1,down[1].x=1;
        for(ll i=2;i<=n;i++){
            up[i]=up[i-1],down[i]=down[i-1];
            if(++up[i].cnt>2) up[i].cnt=1,up[i].x++;
            if(++down[i].cnt>5) down[i].cnt=1,down[i].x++;
            if(a[i]){
                if(up[i].x>a[i]){
                    up[i].x=a[i];
                    up[i].cnt=2;
                }
                else if(up[i].x==a[i]){
                    up[i].cnt=min(up[i].cnt,2ll);
                }
                if(down[i].x<a[i])
                    down[i].x=a[i],down[i].cnt=1;
                if(up[i].x<a[i]||down[i].x>a[i]){
                    printf("-1
    ");
                    return 0;
                }
            }
        }
        if(up[n].cnt==1){
            up[n].x=up[n-1].x;
        }
        if(up[n].x<down[n].x){
            printf("-1
    ");
            return 0;
        }
        printf("%lld
    ",up[n].x);
        tong[up[n].x]=1;
        a[n]=up[n].x;
        for(ll i=n-1;i>=1;i--){
            if(!a[i]){
                ll t=min(a[i+1],up[i].x);
                if(tong[t]==5) t--;
                a[i]=t;
            }
            tong[a[i]]++;
        }
        for(ll i=1;i<=n;i++){
            printf("%lld ",a[i]);
        }
    }
    View Code

    简单的序列

    这是一个简单$dp$,但我觉得很棒在此写下题解

    真的非常简单,

    有一个长度$n$括号序列(只有$"()"$ ),给定其中长度为$m$一段,求满足括号匹配方案数

    $n,m<=1e6$ $n-m<=4000$

    题解

    性质:我们发现一个合法匹配序列左扩号时刻比右括号多(显然),最后左扩号数量等于右括号数量

    设$f[i][j]$表示长度为$i$序列,左扩号比右括号多$j$个方案数

    那么类似的设$g[i][j]$为右括号比左扩号多$j$的方案数

    (其实$f$和$g$值完全一样)

    转移非常简单

    当前括号可能是$($则贡献$f[i][j]=f[i-1][j-1]$为$)$则$f[i][j]=f[i-1][j+1]$

    总贡献$f[i][j]=f[i-1][j-1]+f[i-1][j+1]$

    类似的$g[i][j]=g[i-1][j-1]+g[i-1][j+1]$

    那么思考统计答案

    其实也非常简单

    枚举第一段长度$i$,第一段左扩号比右括号多$j$,设给定序列左扩号比右括号多$j$

    $ans=sumlimits_{i=1}^{i<=n-m} sumlimits_{j=0}^{j<=i} f[i][j]*g[(n-m)-i][j+tot]$

    注意判是否合法

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define A 4040
    const ll mod=1e9+7;
    char c[2020202];
    ll f[A][A];
    ll tot,mint,n,m,ans;
    int main(){
    //    freopen("da.in","r",stdin); freopen("ans.bf","w",stdout);
        scanf("%lld%lld",&n,&m);
        scanf("%s",c+1);
        for(ll i=1;i<=m;i++){
            if(c[i]=='(')
                tot++;
            else tot--;
            if(i==1) mint=tot;
            else mint=min(mint,tot);
        }
        f[0][0]=1;
        for(ll i=1;i<=n-m;i++){
            for(ll j=0;j<=i;j++){
                if(j==0) f[i][j]=f[i-1][j+1];
                else f[i][j]=(f[i-1][j+1]+f[i-1][j-1])%mod;
            }
        }
        for(ll i=0;i<=n-m;i++){
            for(ll j=0;j<=i;j++){
                if(j+mint>=0&&j+tot<=n-m)
                    ans=(ans+f[i][j]*f[(n-m)-i][j+tot]%mod)%mod;
            }
        }
        printf("%lld
    ",ans);
    }
    View Code

    我没数据,也没法提交,和$std$对拍了一下

    下面是我的数据生成及对拍

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int main(){
     4     system("g++ bf.cpp -o bf");
     5     system("g++ sol.cpp -o sol");
     6     system("g++ da.cpp -o da");
     7     int rp=0;
     8     while(++rp){
     9         cout<<rp<<" ";
    10         system("./da");
    11         system("./sol");
    12         system("./bf");
    13         if(system("diff -B -b ans.sol ans.bf")){
    14             puts("WA");
    15             while(1);
    16         }
    17         puts("AC");
    18     }
    19 }
    对拍
     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int main(){
     4     freopen("da.in","w",stdout);
     5     srand(time(NULL));
     6     
     7     int m=rand()%10000+300;
     8     int c=rand()%m+1;
     9     while(m-c>2000){
    10         c=rand()%m+1;
    11     }
    12     cout<<m<<" "<<c<<endl;
    13     for(int i=1;i<=c;i++){
    14         if(rand()%2){
    15             printf("(");
    16         }
    17         else printf(")");
    18     }
    19     cout<<endl;
    20 }
    数据生成

     

  • 相关阅读:
    解决方案:ubuntu无法获得锁,无法管理目录
    ACM经典题目——校门外的树
    【动态规划】01背包问题(通俗易懂,超基础讲解)
    从编程实现角度学习 Faster R-CNN(附极简实现)
    git clone 时,出现‘fatal: HTTP request failed‘
    git clone出现 fatal: unable to access 'https://github.com/...'的解决办法(亲测有效)
    Qt类库的模块
    java 中的 池
    sql 语句
    httpClient 发送http请求
  • 原文地址:https://www.cnblogs.com/znsbc-13/p/11474038.html
Copyright © 2020-2023  润新知