[bzoj4931][SDOI省队集训2017]塔

题目描述

这里写图片描述

DP

感觉这是一种套路题,自己见得太少了……
首先,假如我们定了塔与塔间的相对顺序,就可以知道相邻至少长度的和s,于是得到t=L-s-1,那么答案容易发现是一堆组合数相加得到 Cnt+n
s是n^2级别,可以考虑dp弄出方案数。
考虑从大到小插入,设f[i,j,k]表示已经插入了n~i,目前s为j,有k段。
现在有k段,我们可以把两段用i合并成一段,段数-1,对j的贡献为0。也可以让i丢到某一段的两旁,段数不变,对j的贡献为i。也可以让i自成一段,段数+1,对j的贡献为2*i。
边界比较麻烦,可以看做左边界和右边界是两段,注意不能合并这两段,然后我们可以不插入1,只插入n~2,那么最后一定也只有两段。
组合数只有n项,可以暴力,至于模数,可以拆解质因数做。
上面这个dp不是很好讲清楚,请自行脑补。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=100+10;
int f[2][maxn*maxn+5000][maxn],pri[20],num[20];
int i,j,k,l,t,n,m,L,now,down,up,ndown,nup,sg,cnt,ans,top,xx,yy;
void fj(int m){
    int i,t=floor(sqrt(m)),k=m;
    fo(i,2,t)
        if (k%i==0){
            pri[++top]=i;
            while (k%i==0) k/=i;
        }
}
int qsm(int x,int y){
    if (!y) return 1;
    int t=qsm(x,y/2);
    t=(ll)t*t%m;
    if (y%2) t=(ll)t*x%m;
    return t;
}
void exgcd(int a,int b){
    if (!b){
        xx=1;yy=0;
        return;
    }
    exgcd(b,a%b);
    int x,y;
    x=yy;
    y=xx-(a/b)*yy;
    xx=x;yy=y;
}
int getny(int x){
    exgcd(x,m);
    xx%=m;
    (xx+=m)%=m;
    return xx;
}
int C(int t){
    int i,j,k,l=1,r=1;
    fo(i,1,top) num[i]=0;
    fo(i,t+1,t+n){
        k=i;
        fo(j,1,top){
            while (k%pri[j]==0){
                num[j]++;
                k/=pri[j];
            }
        }
        l=(ll)l*k%m;
    }
    fo(i,1,n){
        k=i;
        fo(j,1,top){
            while (k%pri[j]==0){
                num[j]--;
                k/=pri[j];
            }
        }
        r=(ll)r*k%m;
    }
    l=(ll)l*getny(r)%m;
    fo(i,1,top) l=(ll)l*qsm(pri[i],num[i])%m;
    return l;
}
int main(){
    freopen("tower.in","r",stdin);freopen("tower.out","w",stdout);
    scanf("%d%d%d",&n,&L,&m);
    fj(m);
    f[0][0][2]=1;
    now=0;
    sg=n;
    cnt=0;
    fd(i,n+1,3){
        if (i<=n) ndown=down+i;
        nup=(ndown+i-1)*2;
        fo(j,ndown,nup)
            fo(k,0,min(n-i,i-2)+2)
                f[1-now][j][k]=0;
        fo(j,down,up)
            fo(k,0,min(n+1-i,i-1)+2)
                if (f[now][j][k]){
                    t=(k-2)*(k-3);
                    t+=(k-2)*2;
                    t%=m;
                    if (k>1) (f[1-now][j][k-1]+=(ll)f[now][j][k]*t%m)%=m;
                    t=(k-2)*2;
                    t+=2;
                    (f[1-now][j+i-1][k]+=(ll)f[now][j][k]*t%m)%=m;
                    (f[1-now][j+2*(i-1)][k+1]+=f[now][j][k])%=m;
                }
        down=ndown;
        up=nup;
        /*cnt++;
        if (cnt==2) cnt=0,sg--;*/
        now=1-now;
    }
    fo(j,down,up){
        t=L-j-1;
        if (t<0) break;
        (ans+=(ll)f[now][j][2]*C(t)%m)%=m;
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值