CF Round #589 (Div. 2)E. Another Filling the Grid 组合数学dp

62 篇文章 0 订阅

题目链接: https://codeforces.com/contest/1228/problem/E

题意:

你现在有个 n ∗ n n*n nn 的矩阵,现在要你给每个格子填上一个数字 x ( x ∈ [ 1 , k ] ) x(x\in[1,k]) x(x[1,k]) ,并且要求每一行以及每一列的最小值是 1 1 1 ,问你有多少种填数字的方法。

做法:

只会 O ( n 3 ) O(n^3) O(n3) 的做法,果然还是太菜了。。 O ( n 2 ) O(n^2) O(n2) 的做法表示没弄懂…先写下三次方的做法…

d p [ i ] [ j ] dp[i][j] dp[i][j] 合法代表枚举到第 i i i 列时,仍有 j j j 行还没有符合条件的方法种数。

d p [ i ] [ j ] dp[i][j] dp[i][j] 可以从两种情况转移过来,① d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] 表示没有加进新的边,② d p [ i ] [ x ] ( x ∈ [ j + 1 , n ] ) dp[i][x](x\in[j+1,n]) dp[i][x](x[j+1,n]) ,表示有在新的行上增加 1 1 1

为什么要这样把两种情况分开呢,因为如果有在新的行上增加 1 1 1 的话,原来已经满足条件的行就无所谓是不是一定有 1 1 1 了,而如果没有新增加的话,就必须满足原来的行中至少有一行有 1 1 1 ,这样才能满足在这一列的时候列合法的情况。

d p [ i ] [ j ] + = d p [ i − 1 ] [ j ] ∗ ( k − 1 ) j ( k n − j − ( k − 1 ) n − j ) dp[i][j]+=dp[i-1][j]*(k-1)^j(k^{n-j}-(k-1)^{n-j}) dp[i][j]+=dp[i1][j](k1)j(knj(k1)nj) 表示,未满足的原来的 j j j 行中可以任意取除了 1 1 1 以外的值,而满足条件的 n − j n-j nj 行除了不能全部取非 1 1 1 值以外任意取。

d p [ i ] [ j ] + = ∑ p = j + 1 n d p [ i − 1 ] [ p ] ∗ C p p − j ∗ ( k − 1 ) j ∗ k n − p dp[i][j]+=\sum_{p=j+1}^{n} dp[i-1][p]*C_{p}^{p-j}*(k-1)^j*k^{n-p} dp[i][j]+=p=j+1ndp[i1][p]Cppj(k1)jknp 表示,假设我们从原来的值为 p p p 时转移过来,要从中选 p − j p-j pj 个填上 1 1 1 j j j 行依旧是可以取任意非 1 1 1 的值,除了这 p p p 行之外的行可以任意取。(注意这里是可以任意取,原因上面说过了)。

最后的 d p [ n ] [ 0 ] dp[n][0] dp[n][0] 就是我们要的答案。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i = (int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=255;
ll dp[maxn][maxn],k,C[maxn][maxn];
ll n,qk[maxn],qk1[maxn];
ll quick(ll a,ll b){
    ll ans=1;
    while(b){
        ans=ans*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return ans;
}
void init(){
    rep(i,0,251) C[i][0]=1;
    rep(i,1,251){
        rep(j,1,i){
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
        }
    }
    qk[0]=qk1[0]=1;
    rep(i,1,251) {
        qk[i]=qk[i-1]*k%mod,qk1[i]=qk1[i-1]*(k-1ll)%mod;
    }
}
int main(){
    scanf("%lld%lld",&n,&k);
    init();
    dp[0][n]=1;
    rep(i,1,n){
        rep(j,0,n){
            dp[i][j]=dp[i-1][j]*qk1[j]%mod*((qk[n-j]-qk1[n-j]+mod)%mod)%mod;
            rep(p,j+1,n){
                dp[i][j]=(dp[i][j]+dp[i-1][p]*C[p][p-j]%mod*qk1[j]%mod*qk[n-p]%mod)%mod;
            }
        }
    }
    printf("%lld\n",dp[n][0]);
    return 0;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值