2020暑期牛客多校训练营第七场(G)Topo Counting

Topo Counting

原题请看这里

题目描述:

给定一种有向无环图———的晒肉架图 ( D R G ) (DRG) ,由唯一参数 N N 控制。 D R G DRG 包含 N N 组顶点。第 i i V i V^i 包含 2 N 2N 个顶点: V 1 i V 2 i V 2 N i V^i_1,V ^ i_2,\cdots,V ^ i_ {2N}
D R G DRG 中有两种类型的边:组内边(每组内的边)和内部-组边(组之间的边)。
组内边缘:对于第 i i 组,存在以下组内边缘:
( V j i V j + N i ) (V ^ i_j,V ^ i_ {j+N}) ,对于所有整数 j j 使得 1 j N 1\le j\le N
( V j i V j + 1 i ) (V ^ i_j,V ^ i_ {j+1}) ,对于所有整数 j j 使得 1 j N 1 1\le j\le N-1 N + 1 j 2 N 1 N+1 \leq j \le 2N-1
组间边缘:组边缘存在:
( V i + N 1 V 1 i + 1 ) (V ^ 1_ {i + N},V ^ {i + 1} _1) ,对于所有整数 i i 使得 1 i N 1 1 \le i \le N-1
( V i 1 V 1 + N i ) (V ^ 1_ {i},V ^ {i} _ {1 + N}) ,对于所有整数 i i 使得 2 i N 2 \le i \le N
现在我们想知道以 N N 为参数的 D R G DRG 的拓扑序数。
有向图 G = ( V E ) G=(V,E) 的拓扑顺序是 V ( G ) V(G) 的所有顶点的排列 v p 1 v p 2 v p V ( G ) ) v_ {p_1},v_ {p_2},\cdots,v_ {p_ {| V(G)|}} ) 使得对于所有 i < j i <j ( v p j v p i ) ∉ E ( G ) (v_ {p_j},v_ {p_i}) \not \in E(G)
为了避免计算巨大的整数,请对答案取模 M M

输入描述:

输入仅包含两个整数 N M ( 1 N 3000 N N 2 < M 2 30 ) N,M(1 \le N \le 3000,N * N * 2 <M \le 2 ^ {30}) ,并且 M M 是质数。

输出描述:

输出一个整数,表示答案。

样例:

样例输入1:

2 1073741789

样例输出1:

31

样例输入2:

3 1073741789

样例输出2:

7954100

思路:

首先我们画一张图 ( n = 4 ) (n=4) (爬ppt(官方)的图)
在这里插入图片描述
从中我们可以发现:所有的子图都连向了第一号子图,仔细观察就会发现这个图就像一个晒肉架子… n n 唯一确定了这张图。
看到这张图,我们忽略图与图之间的连边就可以分开考虑,然后 d p dp 求出所有子图 ( ( ) ) 的可能性,最后汇总即可,如果是分开的几个子图,设两个子图上的节点数为 d 1 d_1 , d 2 d_2 ,拓扑序的可能性为 a a b b ,那么两个子图的可能性就是 C d 1 + d 2 d 1 a b C^{d1}_{d1+d2}ab ,这是很好解决的,但是图中是连着的,所以我们要分几种情况考虑。
对于一个子图,进行拓扑时删掉的点的数量上面的肯定比下面的多,而拓扑限制只有与一号子图的第一条连边,不删掉这条边是无法得到下面这块“肉”的,所以我们的 d p dp 需要维护的东西比较多:当前第二排架子删到了第几个;第一排架子删到了第几个;维护的“肉”还剩多少。这时你会发现:我们写出的 d p dp 是一个三维的 d p dp ,超时。
哇我辛辛苦苦求出来的居然超时呜呜呜呜
于是我们又想到如何压缩 d p dp 的维度:
如果在一个完整的图中只删去了一个点,我们只需要一维:
在这里插入图片描述
如果只删去第一行的,那么只需要两维:
在这里插入图片描述
如果把第二行也删了,也只需要两维:
在这里插入图片描述
这样我们就成功的把 d p dp 压成了二维。

A C AC C o d e Code :

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 3005;
const int MAXM = MAXN * MAXN * 2;
int n , mod , dp[ MAXN ][ MAXN ] , mul[ MAXM ] , inv[ MAXM ];
int ksm (int a, int b) {
    int ret = 1;
    while (b) {
        if(b & 1) ret = 1ll * ret * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return ret;
}
int C (int n, int m) {
    if( n < m || m < 0 ) return 0;
    return 1ll * mul[ n ] % mod * inv[ m ] % mod * inv[ n - m ] % mod;
}
int Cat (int n, int m) { return ( C(n * 2 - m, n ) - C( n * 2 - m, n - m - 1 ) + mod ) % mod; }
int main () {
    scanf( "%d%d" , &n, &mod );
    mul[ 0 ] = dp[ 0 ][ 0 ] = 1;
    for ( int i = 1 ; i < MAXM ; ++i ) mul[ i ] = 1ll * mul[ i - 1 ] * i % mod;
    inv[ MAXM - 1 ] = ksm( mul[ MAXM - 1 ] , mod - 2 );
    for ( int i = MAXM - 2 ; ~ i ; --i ) inv[ i ] = 1ll * inv[ i + 1 ] * ( i + 1 ) % mod;
    for ( int i = 0 ; i < n ; ++i )
        for ( int j = 0 ; j < n ; ++j ) {
            if ( i == n - 1 && j == n - 1 ) continue;
            int left = 2 * n * n - min( i , j ) * n * 2 - i - j - 2;
            if ( j == i + 1)
                for ( int k = 0 ; k <= n; ++k )
                    dp[ i + 1 ][ j ] = ( dp[ i + 1 ][ j ] + 1ll * dp[ i ][ j ] * C( left - k , n * 2 - k ) % mod * Cat( n , k ) % mod ) % mod;
            else {
                dp[ i + 1 ][ j ] = ( dp[ i + 1 ][ j ] + dp[ i ][ j ] ) % mod;
                if ( i == j ) dp[ i ][ j + 1 ] = ( dp[ i ][ j + 1 ] + dp[ i ][ j ] ) % mod;
                else dp[ i ][ j + 1 ] = ( dp[ i ][ j + 1 ] + 1ll * dp[ i ][ j ] * C( left , n * 2 ) % mod * Cat( n , 0) % mod ) % mod;
            }
        }
    printf( "%d\n" , dp[ n - 1 ][ n - 1 ] );
}

猜你喜欢

转载自blog.csdn.net/s260127ljy/article/details/107744554