[AGC021F] Trinity 简要题解

题面

题面

题解

考虑以列为维度进行DP。
f ( i , j ) f(i,j) f(i,j)表示 i i i每行都至少有一格为黑色的大小为 i × j i\times j i×j的表格的方案数,那么最终答案就是 ∑ ( n i ) f ( i , m ) \sum\binom{n}{i}f(i,m) (in)f(i,m)
考虑如何转移:初始表格为 0 × 0 0\times 0 0×0 , f ( 0 , 0 ) = 1 f(0,0)=1 f(0,0)=1 ,每次添加一列,在其中添加若干行(不一定都在原来行的下面),新添加的行的 A i A_i Ai 等于新添加的列数。
假设当前列数为 j j j ,分两种情况讨论:

  1. 若行数不变,假设当前行数为 i i i ,列数 j → j + 1 j\rightarrow j+1 jj+1 。所有行的 A i A_i Ai 不变,只考虑 B j + 1 , C j + 1 B_{j+1},C_{j+1} Bj+1,Cj+1 的方案数即可。若不填黑色,则方案数为 1 1 1 ;若填一格黑色,方案数为 i i i ;若填多格黑色,只需考虑最上面一格和最下面一格的位置,方案数为 ( i 2 ) \binom{i}{2} (2i) 。因此总贡献为 f i , j ( 1 + i + ( i 2 ) ) f_{i,j}(1+i+\binom{i}{2}) fi,j(1+i+(2i))
  2. 若行数由 k k k 变为 i i i , j → j + 1 j\rightarrow j+1 jj+1 ,新行的 A i = j + 1 A_i=j+1 Ai=j+1。考虑怎么计算 B j + 1 , C j + 1 B_{j+1},C_{j+1} Bj+1,Cj+1 。发现 B , C B,C B,C 可能由新的行得到,也可能由原来的行得到,直接计算不太好计算。考虑 B , C B,C B,C 之外一格的位置,即 B − 1 , C + 1 B-1,C+1 B1,C+1 ,那么问题变成了从 i + 2 i+2 i+2 的位置中选取 i − k + 2 i-k+2 ik+2 个位置,其中 i + 2 i+2 i+2 个位置对应 [ 0 , i + 1 ] [0,i+1] [0,i+1] ,选取的 i − k i-k ik 个位置为新加的行,选取的 2 2 2 个位置为 B − 1 , C + 1 B-1,C+1 B1,C+1 ,容易发现,每一种选择方案都能对应一种黑色的安排方式。贡献为 ∑ k < i ( i + 2 i − k + 2 ) f k , j \sum_{k<i}\binom{i+2}{i-k+2}f_{k,j} k<i(ik+2i+2)fk,j

得到
f i , j + 1 = ( 1 + i + ( i 2 ) ) f i , j + ∑ k < i ( i + 2 i − k + 2 ) f k , j f_{i,j+1} = (1+i+\binom{i}{2})f_{i,j}+\sum_{k<i}\binom{i+2}{i-k+2}f_{k,j} fi,j+1=(1+i+(2i))fi,j+k<i(ik+2i+2)fk,j

f i , j = ( 1 + i + ( i 2 ) ) f i , j − 1 + ∑ k < i ( i + 2 i − k + 2 ) f k , j − 1 f_{i,j} = (1+i+\binom{i}{2})f_{i,j-1}+\sum_{k<i}\binom{i+2}{i-k+2}f_{k,j-1} fi,j=(1+i+(2i))fi,j1+k<i(ik+2i+2)fk,j1
直接做复杂度 O ( n 2 m ) O(n^2m) O(n2m)
发现转移左边可以 O ( n ) O(n) O(n) 直接转移,考虑右边的部分:
∑ k < i ( i + 2 ) ! ( i − k + 2 ) ! k ! f k , j − 1 \sum_{k<i}\frac{(i+2)!}{(i-k+2)!k!}f_{k,j-1} k<i(ik+2)!k!(i+2)!fk,j1
g 1 = 1 ( i + 2 ) ! g_1=\frac{1}{(i+2)!} g1=(i+2)!1 , g 2 = 1 i ! f i , j − 1 g2=\frac{1}{i!}f_{i,j-1} g2=i!1fi,j1 ,得到:
( i + 2 ) ! ∑ k < i g 1 ( i − k ) g 2 ( k ) (i+2)!\sum_{k<i}g_1(i-k)g_2(k) (i+2)!k<ig1(ik)g2(k)
N T T NTT NTT 做一下即可。

复杂度 O ( n m l o g n ) O(nmlogn) O(nmlogn)

#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
//#define int long long
using namespace std;
const int N=8e3+10,NTTNUM=N*8;
const int M=998244353;
int n,m,lim,limbit,revid[NTTNUM],g1[NTTNUM],g2[NTTNUM],dp[N][N],pw[N],cir[N],ans=0;
int ftp(int b,int p,int mod){
    int r=1;
    while(p){
        if(p&1) r=1ll*r*b%mod;
        b=1ll*b*b%mod;
        p>>=1;
    }
    return r;
}
int INV(int x,int mod){
    return ftp(x,mod-2,mod);
}
void NTT(int len,int *arr,int sign){
    for(int i=0;i<len;i++){
        if(i<revid[i]) swap(arr[i],arr[revid[i]]);
    }
    for(int l=2;l<=len;l<<=1){
        int Wn=ftp((sign==1)?3:INV(3,M),(M-1)/l,M);
        for(int be=0;be<len;be+=l){
            int w=1;
            for(int i=0;i<(l>>1);i++,w=1ll*w*Wn%M){
                int tmp1=arr[be+i],tmp2=1ll*w*arr[be+i+(l>>1)]%M;
                arr[be+i]=(tmp1+tmp2)%M;
                arr[be+i+(l>>1)]=(tmp1-tmp2+M)%M;
            }
        }
    }
    if(sign==-1){
        int inv=INV(len,M);
        for(int i=0;i<len;i++) arr[i]=1ll*arr[i]*inv%M;
    }
}
int C(int x,int y){
    if(x<0||y<0||x<y) return 0;
    return 1ll*pw[x]*cir[y]%M*cir[x-y]%M;
}
void solve(int R){
    memset(g1,0,sizeof(int)*(lim));
    memset(g2,0,sizeof(int)*(lim));
    for(int i=1;i<=n;i++) g1[i]=cir[i+2];// pay attention to this i,it is from [1,n],which is because k<i
    for(int i=0;i<=n;i++) g2[i]=1ll*cir[i]*dp[i][R-1]%M;
    for(int i=0;i<=n;i++) dp[i][R]=(dp[i][R]+1ll*(1ll+i+C(i,2))%M*dp[i][R-1]%M)%M;
    NTT(lim,g1,1);NTT(lim,g2,1);
    for(int i=0;i<lim;i++) g1[i]=1ll*g1[i]*g2[i]%M;
    NTT(lim,g1,-1);
    for(int i=1;i<=n;i++) dp[i][R]=(dp[i][R]+1ll*g1[i]*pw[i+2]%M)%M;
}
int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    pw[0]=1;for(int i=1;i<N;i++) pw[i]=1ll*pw[i-1]*i%M;
    cir[N-1]=INV(pw[N-1],M);for(int i=N-2;i>=0;i--) cir[i]=1ll*cir[i+1]*(i+1ll)%M;
    scanf("%d%d",&n,&m);
    for(lim=1,limbit=0;lim<=(n<<1);lim<<=1,limbit++);
    for(int i=1;i<lim;i++) revid[i]=(revid[i>>1]>>1)|((i&1)<<(limbit-1));
    dp[0][0]=1;
    for(int i=1;i<=m;i++) solve(i);
    for(int i=0;i<=n;i++) ans=(ans+1ll*C(n,i)*dp[i][m]%M)%M;
    printf("%d\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值