bzoj 4313 三维积木 - dp

题目大意:
有三种颜色的大小相同的正方形积木各A,B,C个,你要在一个n*n大小的地盘上搭起这些积木,问有多少种搭积木的方案使得其主视图只有一种颜色。
对1e9+7取模。A,B,C,n<=25。

题解:
考虑枚举哪一个颜色在最前面,剩下两个没有本质区别(算完后乘一个组合数)。每一列独立,做一个卷积即可得到。因此考虑每一列的情况。
现在要计算某一列放a个1和b个2的答案,枚举高度h,那么正面看去恰好h个1,因此先摆出高度恰好为h的方案,然后钦定这看到的h个是1,剩余的随意组合。
高度恰好为h的方案,相当于n个变量的和等于a+b并且每个数值不超过h并且存在至少一个数值是h。第三个限制可以忽略,就是个经典容斥了。这部分O(n^3)。
复杂度O(n^5),可以用NTT优化第一部分的卷积。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define mod 1000000007
#define db long double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define gc getchar()
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
const int A=30,N=30;int fac[A*3+N],facinv[A*3+N],f[A*3][A*3],g[A][A*2],h[N][A][A*2],n,ans,a[10];
inline void upd(int &x,int y) { x+=y,(x>=mod?x-=mod:0); }
inline int sol(int x,int s) { return (s&1)?(x?mod-x:0):x; }
inline void clr(int *a,int n) { memset(a,0,sizeof(int)*(n+1)); }
inline int fast_pow(int x,int k,int ans=1) { for(;k;k>>=1,x=(lint)x*x%mod) (k&1)?ans=(lint)ans*x%mod:0;return ans; }
inline int prelude(int n)
{
    rep(i,fac[0]=1,n) fac[i]=(lint)fac[i-1]*i%mod;
    facinv[n]=fast_pow(fac[n],mod-2);
    for(int i=n-1;i>=0;i--) facinv[i]=(i+1ll)*facinv[i+1]%mod;
    return 0;
}
inline int C(int n,int m) { if(n<0||m<0||n<m) return 0;return (lint)fac[n]*facinv[n-m]%mod*facinv[m]%mod; }
inline int calc_f(int s,int n)
{
    rep(i,0,s)
    {
        rep(j,0,i) rep(k,0,n) upd(f[i][j],(lint)sol(C(n,k),k)*C(i+n-k*(j+1)-1,n-1)%mod);
        for(int j=i;j;j--) f[i][j]-=f[i][j-1],(f[i][j]<0?f[i][j]+=mod:0);
    }
    return 0;
}
inline int calc_g(int A,int B) { rep(a,0,A) rep(b,0,B) rep(i,0,a) upd(g[a][b],(lint)f[a+b][i]*C(a-i+b,a-i)%mod);return 0; }
inline int calc_h(int A,int B,int n)
{
    h[0][0][0]=1;
    rep(i,0,n-1) rep(a,0,A) rep(b,0,B) if(h[i][a][b])
        rep(c,0,A-a) rep(d,0,B-b) upd(h[i+1][a+c][b+d],(lint)h[i][a][b]*g[c][d]%mod);
    return 0;
}
inline int calc(int x,int y,int z) { return (lint)C(a[y]+a[z],a[y])*h[n][a[x]][a[y]+a[z]]%mod; }
int main()
{
    a[1]=inn(),a[2]=inn(),a[3]=inn(),n=inn(),sort(a+1,a+4),swap(a[1],a[3]);
    prelude(a[1]+a[2]+a[1]+n),calc_f(a[1]+a[2]+a[1],n),
    calc_g(a[1],a[1]+a[2]),calc_h(a[1],a[1]+a[2],n);
    upd(ans,calc(1,2,3)),upd(ans,calc(2,1,3)),upd(ans,calc(3,1,2));
    return !printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值