[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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章