夏令营501-511NOIP训练18——高二学堂

传送门:QAQQAQ

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

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

思路:依旧神仙题。。满分现阶段无望,现在逐步深入讲部分分的做法

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\n",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\n",ans);
    }
    return 0;
}

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

考虑到80分是矩阵快速幂,我们把剩下的两位压进一维(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\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Forever-666/p/11291697.html