「题解」AT4515 [AGC030F] Permutation and Minimum

题目链接

学OI以来的第一道黑题,先在这里祭一下。

  • 第一道黑题祭

于是就打算来写题解。

题面大意

有一个 \(2N\) 个数的序列 \(A\),从 \(1\)\(2N\) 标号。你要把 \(1 \sim 2N\) 这些数填进去,使它形成一个排列。

但是已经有一些位置强制填了特定的数了,输入时会给出。

最后令长度为 \(N\) 的序列 \(B\) 为:令 \(B_i = \min\{A_{2 i - 1}, A_{2 i}\}\)

询问所有方案中能得到的不同的 \(B\) 的数量。

\(1 \le N \le 300\)

思路

我们要求一个长度为 \(N\) 的序列 \(B\),然后是序列 \(A\) 中的数两两配对中的最小值,我们先考虑把他变成有序的,然后从大到小来统计,因为如果从小到大的话我们可能会对已经统计过的数字进行再次统计,导致答案偏大,然后因为数字大小是小于等于 600 的,所以我们可以直接上桶来搞。

首先对于 \(A_{2i-1}\)\(A_{2i}\) 都已经给出的数对肯定是不会对答案作出贡献的,所以我们标记一下,在之后统计的时候不计入答案中,所以对答案能作出贡献的肯定是如同 ( -1 , x ) 与 ( -1 , -1 )的形式,我们称前者为形式 1 , 后者为形式 2 。对于后者我们是能够随意变换顺序的,所以我们就可以统计数量然后最后对答案乘上这个数量的阶乘。

显然这是一个计数 DP ,我们考虑如何 DP。

设一个状态 \(f_{i,j,k}\) 表示我们已经统计到了第 \(i\) 个数,还剩下 \(j\) 对我们已经填了一个数的形式 2 的数对, 还有 \(k\) 对没有填的形式 1 的数对。

那么对于形式 1 ,和形式 2 的数对,我们先预处理出他们分别有多少个。然后再将其放入一个数组中方便查询到底是哪一种。

首先对于一个已经存在与形式 1 的数对中的数,我们可以填或者不填,如果不填那么我们就会从 \(f_{i - 1, j, k}\) 转移到 \(f_{i, j, k + 1}\) ,如果填的话, 我们必拉一个已经填了一半的形式 2 的数对过来拼成一个完整的数对,所以这时 \(j > 0\) ,然后我们就会从 \(f_{i - 1, j ,k}\) 转移到 \(f_{i, j - 1, k}\)

然后就是对于剩下的数,他们是自由数,首先他们可以填入一个空的数对,那么就会从 \(f_{i - 1, j, k}\) 转移至 \(f_{i, j + 1, k}\), 其次可以填入一个已经完成了一半的形式 2 的数对,此时 \(j > 0\) ,那么就会从 \(f_{i - 1,j,k}\) 转移至 \(f_{i , j-1, k}\), 最后还可以填入一个形式 1 的数对,且此时 \(k > 0\),那么就会从 \(f_{i - 1,j,k}\) 转移至 \(f_{i, j, k - 1}\) ,又因为形式 1 的数对的位置是固定的, 而此时又有 \(k\) 个此形式的数对,所以我们有 \(k\) 种填法,需要对 \(f_{i - 1, j , k} \times k\) 后转移。

边界为 \(f _ {0 ,0 ,0} = 1\) ,注意取模

下为代码

#include <bits/stdc++.h>
using namespace std;
#define R register int
const int N = 300 + 10, mod = 1e9 + 7;
int f[N << 1][N][N], a[N << 1], cnt = 0, tot = 0;
int num = 0, b[N << 1];
bool vis[N << 1], use[N << 1];
signed main()  {
    int n; scanf("%d", &n);
    for(R i = 1; i <= n; i ++) {
        int y = i << 1, x = y - 1;
        scanf("%d%d", &a[x], &a[y]);
        if(a[x] == -1 && a[y] == -1) cnt ++; //对形式 2 的数对进行统计
        else if(a[x] > 0 && a[y] > 0) vis[a[x]] = vis[a[y]] = true; //如果两个数都已经确定则打上标记
        else {
            tot ++; 
            if(a[x] == -1) use[a[y]] = true;
            else use[a[x]] = true;
        }//对于形式 1 的数对进行统计,并且统计出这种数对中不为 -1 的数是哪些
    }
    int n2 = 0; f[0][0][0] = 1; n <<= 1;
    for(R i = n; i >= 1; i --)
        if(!vis[i]) b[++ n2] = i;
    //将会对答案作出贡献的数进行从大到小的排序(
    for(R i = 1; i <= n2; i ++)
        for(R j = 0; j <= cnt + tot; j ++) //自由数能填入所有会对答案造成贡献的数
            for(R k = 0; k <= tot; k ++) {
                if(use[b[i]]) {
                    f[i][j][k + 1] = (f[i][j][k + 1] + f[i - 1][j][k]) % mod;
                    if(j > 0) f[i][j - 1][k] = (f[i - 1][j][k] + f[i][j - 1][k]) % mod;
                }
                else {
                    f[i][j + 1][k] += (f[i - 1][j][k] + f[i][j + 1][k]) % mod;
                    if(j > 0) f[i][j - 1][k] = (f[i - 1][j][k] + f[i][j - 1][k]) % mod;
                    if(k > 0) f[i][j][k - 1] = (f[i][j][k - 1] + 1ll * f[i - 1][j][k] * k % mod) % mod;
                }
            }

    int ans = f[n2][0][0]; 
    for(R i = 1; i <= cnt; i ++) ans = (1ll * ans * i)  % mod;// 乘上形如( -1 , -1 )的数对的阶乘
    printf("%d\n", ans);
    return 0;
}
posted @ 2020-11-11 21:23  Van_樣年华  阅读(109)  评论(1编辑  收藏  举报