zoukankan      html  css  js  c++  java
  • 夏令营501-511NOIP训练18——高二学堂

    传送门:QAQQAQ

    题意:给你一个数$n$,把它拆分成至多$k$个正整数,使得这些数的和等于$n$且每一个正整数的个数不能超过$4$

    拆分的顺序是无序的,但取出每一个数方案是不同的(例如我要拆$1$,就有$4$种方案,因为$4$个“1”是不同的)

    思路:依旧神仙题。。满分好像是什么BM算法,但这道题可以用矩阵快速幂卡过去

    40分:暴力,我们把$n$种数拆分成$4*n$个数,然后跑01背包就可以了,防止MLE,可以开滚动,但注意转移时要反着来

    #include<bits/stdc++.h>
    using namespace std;
    const int MOD=1000000009;
     
    int dp[10001][21],n,k,a[40005];
     
    int main()
    {
        while(scanf("%d%d",&n,&k)!=EOF)
        {
            if(k>n) k=n;
            if(n==0) break;
            memset(dp,0,sizeof(dp));
            dp[0][0]=1;
            for(int i=1;i<=4*n;i++) a[i]=(i+3)/4;
            for(int i=1;i<=4*n;i++)
            {
                for(int j=min(i,k);j>=1;j--)
                {
                    for(int t=n;t>=a[i];t--)
                    {
                        dp[t][j]=(dp[t][j]+dp[t-a[i]][j-1])%MOD;
                    }
                }
            }
            int ans=0;
            for(int i=1;i<=k;i++) ans=(ans+dp[n][i])%MOD;
            printf("%d
    ",ans);
        }
        return 0;
    }

    60分:我们考虑转移时优化一维——即把枚举数的ID这一维优化掉

    我们对于转移进行分类讨论:

    1.若转移前数列中没有1,那么我们就可以一次性往现数列中加1,并对新加上的1进行“不同化”——即对加进的t个1乘上C(4,t)(这样可以保证2,3,4……都已进行“不同化”)

    2.若转移中有1,那么我们就把数列中所有数都加一,使其没有1

    我们可以设$dp[i][j][bl]$为和为$i$,取了$j$个数,数列中是否含有1(这种设状态较好理解)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll MOD=1000000009;
    
    ll dp[100001][21][2],n,k;
    
    void add(ll &x,ll y)
    {
        x+=y;
        if(x>=MOD) x-=MOD;
    }
    ll c4[5]={1,4,6,4,1};
    
    int main()
    {
        while(scanf("%lld%lld",&n,&k)!=EOF)
        {
            if(k>n) k=n;
            if(n==0&&k==0) break;
            memset(dp,0,sizeof(dp));
            dp[0][0][0]=1;
            for(ll i=1;i<=n;i++)
            {
                for(ll j=1;j<=min(i,k);j++)
                {
                    if(i>j)
                    {
                        add(dp[i][j][0],dp[i-j][j][0]);
                        add(dp[i][j][0],dp[i-j][j][1]);
                    }
                    for(ll t=1;t<=min(j,4LL);t++)
                        add(dp[i][j][1],dp[i-t][j-t][0]*c4[t]%MOD); 
                }
            }
            ll ans=0;
            for(ll i=1;i<=k;i++)
            {
                add(ans,dp[n][i][0]);
                add(ans,dp[n][i][1]);
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }

    当然,为了后面的满分代码更方便,我们考虑对状态进行降维,我们把最后bl去掉,把“所有数加1”和“往数组里加1”两个操作一起进行

    考虑到满分是矩阵快速幂,我们把剩下的两位压进一维(k比较小,所以可以压)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll MOD=1000000009;
    
    ll dp[1000001],n,k;
    
    void add(ll &x,ll y)
    {
        x+=y;
        if(x>=MOD) x-=MOD;
    }
    ll c4[5]={1,4,6,4,1};
    ll id(ll x,ll y)
    {
        return x*10+y;
    }
    
    int main()
    {
        while(scanf("%lld%lld",&n,&k)!=EOF)
        {
            if(k>n) k=n;
            if(n==0&&k==0) break;
            memset(dp,0,sizeof(dp));
            dp[0]=1;
            for(ll i=1;i<=n;i++)
            {
                for(ll j=1;j<=min(i,k);j++)
                {
                    for(ll t=0;t<=min(j,4LL);t++)
                        add(dp[id(i,j)],dp[id(i-j,j-t)]*c4[t]%MOD); 
                        //先让原数组j-t个数都加1,再加入t个1 
                }
            }
            ll ans=0;
            for(ll i=1;i<=k;i++)
            {
                add(ans,dp[id(n,i)]);
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }

     100分:第二天补的。。。其实dp并不需要压入一维,而且dp压维因为转移的时候会涉及到dp[0][0],所以dp压维有点不方便,我们只需要在把dp数组弄进矩阵的时候压一压就可以了

    我们考虑转移需要的最早的dp和转移的周期,本来想一个一个递推的,但这种要分类j是否大于4,所以每次矩阵都会改变,无法使用矩阵快速幂。

    所以我们加大周期:每十个一次转移,$dp[i][j]$由$dp[i-j][j-t]$转移而来,所以我们对于要更新出的dp[m+1][j],枚举所有合法的t(此时t=j不合法,我们已经枚举了$dp[k][k]$前的所有状态,所以转移前状态$i$一定都大于0,所以此时$j=0$不合法)

    所以总结一下,矩阵快速幂由这些要点:转移需要的最早的值,转移周期,和初始矩阵边界条件

    我们把前$k*(k-1)$列都设为把ANS矩阵往前推10位,后k列根据$dp[m+1][j]$的转移前缀和系数在适当的位置填上$C(4,t)$,为了方便理解,下面打印一个k=5时的初始转移矩阵

    (ANS矩阵时横着的,转移时ANS=ANS*B)

    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 $
    $1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 $
    $0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 $
    $0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 $
    $0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 $
    $0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 $
    $0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 $
    $0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 $
    $0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 4 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 $
    $0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 $

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll MOD=1000000009;
    
    ll dp[31][31];
    int n,k;
    
    void add(ll &x,ll y)
    {
        x+=y;
        if(x>=MOD) x-=MOD;
    }
    ll c4[5]={1,4,6,4,1};
    int id(int x,int y)
    {
        return (x-1)*k+y;
    }
     
    struct matrix{
        ll a[111][111];
        int n,m;
        matrix(){}
        matrix(int n,int m):n(n),m(m)
        {
            memset(a,0,sizeof(a));
        }
        void print()
        {
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=m;j++) printf("%lld ",a[i][j]); 
                puts("");
            }
        }
    };
    
    matrix operator * (matrix A,matrix B)
    {
        matrix C(A.n,B.m); 
        int t=min(A.m,B.n);
        for(int i=1;i<=C.n;i++)
        {
            for(int j=1;j<=C.m;j++)
            {
                for(int p=1;p<=t;p++)
                    add(C.a[i][j],A.a[i][p]*B.a[p][j]%MOD);
            }
        }
        return C;
    }
    
    matrix qpow(matrix B,matrix A,int y)
    {
        matrix Z=A;
        while(y)
        {
            if(y&1) Z=Z*B;
            B=B*B;
            y>>=1;
        }
        return Z;
    }
    
    void ready()
    {
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=10;i++)
        {
            for(int j=1;j<=min(k,i);j++)
            {
                for(int t=0;t<=min(j,4);t++)
                    add(dp[i][j],dp[i-j][j-t]*c4[t]%MOD); 
                    //先让原数组j-t个数都加1,再加入t个1 
            }
        }
    }
    
    void make_matrix()
    {
        matrix A(k*k,k*k);
        matrix B(k*k,k*k);
        for(int i=1;i<=A.m;i++) A.a[i][i]=1;
        for(int i=1;i<=k*k-k;i++) B.a[i+k][i]=1;
        for(int j=1;j<=k;j++)
        {
            for(int t=0;t<=min(4,j-1);t++) //和已经大于k,不可能再从取数为0的情况下一次转移而来 
            //dp[2][1]就从dp[1][1]转移而来 
            {
                B.a[id(k+1-j,j-t)][k*k-k+j]=c4[t];
            }
        }
        matrix C(1,k*k);
        for(int i=1;i<=k;i++)
        {
            for(int j=1;j<=k;j++)
            {
                C.a[1][id(i,j)]=dp[i][j];
            }
        } 
        B=qpow(B,A,n-k);
        C=C*B;
        ll ans=0;
        for(int i=1;i<=k;i++) 
        {
            add(ans,C.a[1][id(k,i)]);
        }
        printf("%lld
    ",ans%MOD);
    }
    
    int main()
    {
        while(scanf("%d%d",&n,&k)!=EOF)
        {
            if(k>n) k=n;
            if(n==0&&k==0) break;
            ready();
            if(n<=k)
            {
                ll ans=0;
                for(int i=1;i<=k;i++) add(ans,dp[n][i]);
                printf("%lld
    ",ans);
            }
            else make_matrix();
        }
        return 0;
    }
  • 相关阅读:
    Kentico的UIPager的pagesize不工作
    哈啰单车流量问题
    安卓手机无限重启是怎么回事?
    Kentico的翻译功能
    Remote Desktop Free Manager
    访问存储在服务器中的CDR
    保持观察者状态达到跳出或不迷
    高通IPQ4019
    satixnet satellite modem T1000e
    802.11n 中HT20 HT40的区别和信道划分及plus,minus含义
  • 原文地址:https://www.cnblogs.com/Forever-666/p/11291697.html
Copyright © 2011-2022 走看看