[JZOJ5127]塔

题目大意

有一条[1,l] 的数轴,要在上面建造n 座塔,每座塔的座标要两两不同,且为整点。塔有编号,且每座塔都有高度,编号为i 的塔高度为i 。对于一座塔,需要满足其与前面和后面的塔的距离都大于等于自身高度(如果不存在则没有限制)。
问有多少种建造方案。答案对m 取模。塔不要求按照编号顺序建造。

n100,1l109,1m109


题目分析

先考虑这样一个暴力:枚举塔的排列顺序,然后我们可以计算出按顺序安放这些塔至少需要的空间,假设其为s
然后我们剩下l1s 的空间要分配给n+1 个空位,由隔板原理即可算得方案数为(ls+n1n)
可以发现,塔的排列顺序固然有很多种,但是这些排列计算出来的s 最多不过O(n2) 级别。这个提示我们去使用动态规划算法。
考虑从大到小放塔。考虑放置塔i ,如果这个塔夹在两个之前放过的塔旁边,那么其对s 就没有贡献;如果它只是挨在一个之前放过的塔边,那么其对s 就有i 的贡献;否则它一个塔都不挨着,对s 就有2i 的贡献。当然,最后所有塔都要挨在一起。
fi,j,k 表示我们放到第i 个塔,对s 的贡献和为j ,有k 个空余的段可以安放。那么根据上面三种情况就会有三种转移:

fi1,j,k1fi1,j+i1,kfi1,j+2(i1),k+1fi,j,k×kfi,j,k×2kfi,j,k×k

当然还要注意一下边界情况,我的处理方法是在两边各加一个高塔,最后所有塔合并在一起。
直接做貌似会MLE+TLE。首先我们要滚动第一维,其次后面两维我们都可以算出一个并不需要太紧的上下界来枚举,加上这两个优化就可以通过了。
至于组合数,你可以矩阵乘法加递推,也可以分解质因数算一算。这个自己脑补一下就好了。
时间复杂度O(n4)

代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cassert>
#include <cstdio>

using namespace std;

const int N=105;

int pri[N],cnt[N],tmp[N],mip[N],pid[N];
int f[2][N*N][N];
bool mark[N];
int n,m,l,ans;

void pre()
{
    mark[1]=1,mip[1]=1;
    for (int i=2;i<=n;++i)
    {
        if (!mark[i]) pri[++pri[0]]=mip[i]=i,pid[i]=pri[0];
        for (int j=1;j<=pri[0];++j)
        {
            if (1ll*pri[j]*i>n) break;
            mark[pri[j]*i]=1,mip[pri[j]*i]=pri[j];
            if (!(i%pri[j])) break;
        }
    }
    for (int i=1;i<=n;++i)
        for (int x=i;x!=1;)
            for (int y=mip[x];!(x%y);x/=y,++cnt[pid[y]]);
}

bool dp()
{
    f[0][0][1]=1;
    bool x=1,y=0;
    for (int i=n+1,upper=0,lower=0;i>1;lower+=i<=n?i:0,--i,upper+=i<<1)
    {
        x^=1,y^=1,memset(f[y],0,sizeof f[y]);
        for (int j=lower;j<=upper&&j<=n*n;++j)
            for (int k=1;k<=i+2&&k<=n-i+3;++k)
            {
                if (!f[x][j][k]) continue;
                (f[y][j][k-1]+=1ll*f[x][j][k]*k%m)%=m;
                (f[y][j+i-1][k]+=1ll*f[x][j][k]*(k*2)%m)%=m;
                (f[y][j+(i-1<<1)][k+1]+=1ll*f[x][j][k]*k%m)%=m;
            }
    }
    return y;
}

int mult(int st,int en)
{
    int ret=1;
    for (int i=1;i<=pri[0];++i) tmp[i]=cnt[i];
    for (int i=st;i<=en;++i)
    {
        int x=i;
        for (int j=1;j<=pri[0];++j)
            for (;tmp[j]&&!(x%pri[j]);x/=pri[j],--tmp[j]);
        ret=1ll*ret*x%m;
    }
    int tot=0;
    for (int j=1;j<=pri[0];++j) tot+=!tmp[j];
    assert(tot==pri[0]);
    return ret;
}

void calc()
{
    pre(),ans=0;
    for (int i=0,x=dp();i<=n*n&&i<=l;++i)
        if (f[x][i][0]) (ans+=1ll*f[x][i][0]*mult(l-i,l-i+n-1)%m)%=m;
}

int main()
{
    freopen("tower.in","r",stdin),freopen("tower.out","w",stdout);
    scanf("%d%d%d",&n,&l,&m),calc(),printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章