AtCoder4515 [AGC030F] Permutation and Minimum(dp)

problem

洛谷链接

solution

一个 A i A_i Ai 只会影响一个 B i B_i Bi B i B_i Bi 之间的决定因素 A A A 是不会有交的。

所以如果相邻两个对同一个 B i B_i Bi 影响的 A 2 i , A 2 i − 1 A_{2i},A_{2i-1} A2i,A2i1 都是确定的,那么 B i B_i Bi 也就确定了。

这是不会对方案数产生贡献的,提前预处理掉这些位置,接下来的计算不考虑这些出现的值。

同时,如果都不确定,这些配对位置的值可以互换,即原本 − 1 -1 1 的位置是无序的,所以最后的答案要乘上一个阶乘。

B i B_i Bi 是取较小值,容易想到从大到小考虑填的数,那么 B i B_i Bi 就仅由 A 2 i , A 2 i − 1 A_{2i},A_{2i-1} A2i,A2i1 后填的数决定。

下面将 A 2 i − 1 , A 2 i A_{2i-1},A_{2i} A2i1,A2i 没全填的称为“未配对”。

f ( i , j , k ) : f(i,j,k): f(i,j,k): 已考虑到数第 i i i 大时,有 j j j 个明确指定的数还未配对,有 k k k 个原本未知 − 1 -1 1 然后填了个数也还未配对 的方案数。

考虑由 f ( i , j , k ) f(i,j,k) f(i,j,k) 转移到 i + 1 i+1 i+1,即 A i + 1 A_{i+1} Ai+1 的配对情况。

  • 如果 A i + 1 A_{i+1} Ai+1 原本是指定的数, A i + 1 ≠ − 1 A_{i+1}\neq -1 Ai+1=1

    • 可以和未知数配对, f ( i , j , k ) → f ( i + 1 , j , k − 1 ) f(i,j,k)\rightarrow f(i+1,j,k-1) f(i,j,k)f(i+1,j,k1)
    • 可以新增一个已知数的未配对, f ( i , j , k ) → f ( i , j + 1 , k ) f(i,j,k)\rightarrow f(i,j+1,k) f(i,j,k)f(i,j+1,k)
  • 如果 A i + 1 = − 1 A_{i+1}=-1 Ai+1=1,未知。

    • 可以新增一个未知数的未配对, f ( i , j , k ) → f ( i , j , k + 1 ) f(i,j,k)\rightarrow f(i,j,k+1) f(i,j,k)f(i,j,k+1)

    • 可以和未知数配对, f ( i , j , k ) → f ( i + 1 , j , k − 1 ) f(i,j,k)\rightarrow f(i+1,j,k-1) f(i,j,k)f(i+1,j,k1)

    • 可以和已知数配对, f ( i , j , k ) × j → f ( i + 1 , j − 1 , k ) f(i,j,k)\times j\rightarrow f(i+1,j-1,k) f(i,j,k)×jf(i+1,j1,k)

      因为已知数的位置是固定的,所以 A i + 1 A_{i+1} Ai+1 j j j 个已知数配对都是不同的情况。

      而和未知数的配对是不考虑 × k \times k ×k 的。本来位置就不固定,在最后的阶乘时候已经算入了,这里再乘就算重了。

初始状态 f ( 0 , 0 , 0 ) = 1 f(0,0,0)=1 f(0,0,0)=1

code

#include <bits/stdc++.h>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 605
int n, m, cnt, fac;
int a[maxn], c[maxn], g[maxn];
int f[maxn][maxn][maxn];

signed main() {
    scanf( "%lld", &n ), n <<= 1;
    for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );
    for( int i = 1;i <= n;i += 2 ) {
        if( a[i] == -1 and a[i + 1] == -1 ) cnt ++;
        else if( ~ a[i] and ~ a[i + 1] ) g[a[i]] = g[a[i + 1]] = 2;
        else {
            if( ~ a[i] ) g[a[i]] = 1;
            else g[a[i + 1]] = 1;
        }
    }
    for( fac = 1;cnt;fac = fac * cnt % mod, cnt -- );
    for( int i = n;i;i -- ) if( g[i] ^ 2 ) c[++ m] = i;
    f[0][0][0] = 1;
    for( int i = 0;i < m;i ++ )
        for( int j = 0;j <= n;j ++ )
            for( int k = 0;k <= n;k ++ )
                if( ! f[i][j][k] ) continue;
                else 
                    if( g[c[i + 1]] ) { //已知数
                        ( f[i + 1][j + 1][k] += f[i][j][k] ) %= mod; //新增一个未匹配的已知数
                        if( k ) ( f[i + 1][j][k - 1] += f[i][j][k] ) %= mod; //和未知数匹配
                    }
                    else { //未知数
                        ( f[i + 1][j][k + 1] += f[i][j][k] ) %= mod; //新增一个未匹配的未知数
                        if( j ) ( f[i + 1][j - 1][k] += f[i][j][k] * j % mod ) %= mod; //和已知数配对
                        if( k ) ( f[i + 1][j][k - 1] += f[i][j][k] ) %= mod; //和未知数匹配
                    }
    printf( "%lld\n", f[m][0][0] * fac % mod );
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值