#NOIP模拟赛#排列问题(DP)

31 篇文章 0 订阅
27 篇文章 0 订阅




这个题,是一个DP,令人惊讶,我当时根本就没往这方面想,还是题见得少了

同学有一个DP解法,个人感觉比标解好理解得多,具体如下:

如图:

将数字1 ~ N从大到小填

定义Dp[full][half][sum]表示

已经填了full个格子(上下对应都填了, 如:上4下5)

有2 * half个格子填了一半(如:红色点的两个格子,由于这种格子必然是偶数个,所以除2)

已经填的数的总和是sum

Dp值是此时的方案数。


这题的转移有三种情况,

以上图为例,现在应该填now = 3:

1,分别填在两个蓝色格子内,并使他们无法组成一个新的full,此时有N - full - (half - 1) * 2个上下同时为空的格子,所以:

Dp[full][half][sum] += Dp[full][half - 1][sum - 2 * now] * (N - full - (half - 1) * 2) * (N - full - (half - 1) * 2 - 1) % MOD;


2,使成为一个新的full(直接填在一对对应的空格子里(蓝色), 或者填一个在红色格子里,再填一个在蓝色格子里,half值不变,将损失1对full):

Dp[full][half][sum] += Dp[full - 1][half][sum - now] * (half  * 2 * (N - half *2 - (full - 1)) + N - half * 2 - (full - 1));


3,使成为一个新的full(填在两个半格里(红色),将损失1对half):

Dp[full][half][sum] += Dp[full - 2][half + 1][sum] * (half + 1) * (half + 1);


最后的答案是 Σ Dp[N][0][sum](sum >= K)


下面是标解,和上面的解法有所不同:



Code:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;

const int MOD = 1e9 + 7;

int N, K, L, Ans;
int Dp[55][55][55 * 55];

bool getint(int & num){
    char c; int flg = 1;    num = 0;
    while((c = getchar()) < '0' || c > '9'){
        if(c == '-')    flg = -1;
        if(c == -1) return 0;
    }
    while(c >= '0' && c <= '9'){
        num = num * 10 + c - 48;
        if((c = getchar()) == -1)   return 0;
    }
    num *= flg;
    return 1;
}

int P(int x){
    int rt = 1;
    for(int i = 2; i <= x; ++ i)
        rt = 1ll * rt * i % MOD;
    return rt;
}

int main(){
    freopen("data1.in", "r", stdin);
    //freopen("permutation.in", "r", stdin);
    //freopen("permutation.out", "w", stdout);
    getint(N), getint(K);
    int up = N * N;
    Dp[0][0][0] = 1;
    for(int i = 1; i <= N; ++ i){
        int now = N - i + 1;
        for(int full = 0; full <= i; ++ full){
            int half = i - full;
            for(int s = 0; s <= up; ++ s){
                if(s >= now * 2 && N - full - (half - 1) * 2 > 0)
                    Dp[full][half][s] = (Dp[full][half][s] + 1ll*Dp[full][half-1][s-(now << 1)]*(N-full-((half-1)<<1))%MOD*(N-full-((half-1)<<1)-1)%MOD)%MOD;
                if(full && s >= now)
                    Dp[full][half][s] = (Dp[full][half][s] + 1ll*Dp[full-1][half][s-now]*((half*(N-(half<<1)-(full-1))<<1)%MOD+N-(half<<1)-(full-1))%MOD)%MOD;
                if(full >= 2)
                    Dp[full][half][s] = (Dp[full][half][s] + 1ll*Dp[full-2][half+1][s]*(half+1)%MOD*(half+1)%MOD)%MOD;
            }
        }
    }
    int Ans = 0;
    for(int i = K; i <= up; ++ i)
        Ans = (Ans + Dp[N][0][i]) % MOD;
    printf("%d\n", Ans);
    return 0;
}





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值