1. 程式人生 > 實用技巧 >Solution -「AGC 019E」「AT 2704」Shuffle and Swap

Solution -「AGC 019E」「AT 2704」Shuffle and Swap

\(\mathcal{Description}\)

  Link.

  給定 \(01\) 序列 \(\{A_n\}\)\(\{B_n\}\),其中 \(1\) 的個數均為 \(k\)。記 \(A\)\(1\) 的位置為 \(\{a_k\}\)\(B\) 中的為 \(\{b_k\}\)。現任意排列 \(\{a_k\}\)\(\{b_k\}\),然後依次交換 \(A_{a_i}\)\(A_{b_i}\)\(i=1,2,\dots,k\)。求使操作完成後 \(A=B\) 的排列方案數對 \(998244353\) 取模的結果。

  \(n\le10^4\)

\(\mathcal{Solution}\)

  首先轉化題意——每次操作用 \(A\) 的一個 \(1\)\(B\) 中一個需要 \(1\) 的位置交換,共進行 \(k\) 次操作,求滿足條件的方案數。

  我們定義 \(A_i=1\land B_i=0\) 的位置為“富餘點”——它們需要為其它位置提供 \(1\)\(A_i=B_i=1\) 的位置為“公共點”——它們已經滿足條件,但可以作為傳遞 \(1\) 的載體;\(A_i=0\land B_i=1\) 的位置為“缺失點”——它們是“富餘點”需要提供到的位置。

  那麼,只需要每個“富餘點”的 \(1\) 都傳遞給“缺失點”,\(A\) 就與 \(B\) 相等。考慮一次從一個“富餘”到“缺失”的傳遞,如圖:

  其中三角形為“缺失點”,圓形為“公共點”(任意多個,亦可不存在),方形為“富餘點”。注意若存在“公共點”,傳遞是不能反向的,這樣會導致與三角形相鄰的公共點的 \(1\) 變為 \(0\)。(某課件的筆誤,注意一下 www。)


  考慮 DP,記 \(f(i,j)\) 表示在傳遞鏈中用了 \(i\) 個“公共點”,用了 \(j\) 個“富餘點”(即有 \(j\) 條傳遞鏈)時,傳遞鏈中的方案數。邊界為 \(f(0,0)=1\),轉移:

\[f(i,j)=f(i-1,j)\cdot ij+f(i,j-1)\cdot j^2 \]

  前一項,將一個公共點加入一條傳遞鏈的末尾。有 \(j\) 條鏈,新的點可以與已有的 \(i-1\)

個點交換(不是交換操作順序,而是直接交換鏈中位置),故有係數 \(ij\)

  後一項,新建一條“缺失”-“富餘”鏈。首先拿出新的一對“缺失點”和“富餘點”,仍考慮到原來的“缺失點”或“富餘點”可以和新點交換位置,故有係數 \(j^2\)

  最後統計答案,發現“公共點”沒有必要在鏈中用完,設在鏈中用 \(a\) 個“公共點”,還剩下 \(b\) 個。那麼在所有“公共點”中選出 \(b\) 個,方案數 \(\binom{a+b}{b}\);公共點內部方案 \((b!)^2\);把這 \(b\) 次操作安排進總共 \(k\) 次操作裡,方案數 \(\binom{k}{b}\)。所以設“公共點”有 \(s\) 個,“富餘點”有 \(t\) 個,答案為(\(i\) 列舉的是不在鏈中的“公共點”個數):

\[\sum_{i=0}^{s}\binom{s}i\binom{s+t}{i}(i!)^2f(s-i,t) \]

  複雜度 \(\mathcal O(n^2)\)

\(\mathcal{Code}\)

#include <cstdio>
#include <cstring>

const int MAXL = 1e4, MOD = 998244353;
int n, sur, bal, f[MAXL + 5][MAXL + 5];
int fac[MAXL + 5], ifac[MAXL + 5];
char A[MAXL + 5], B[MAXL + 5];

inline void addeq ( int& a, const int b ) { if ( ( a += b ) >= MOD ) a -= MOD; }
inline int mul ( long long a, const int b ) { return ( a *= b ) < MOD ? a : a % MOD; }

inline int qkpow ( int a, int b ) {
	int ret = 1;
	for ( ; b; a = mul ( a, a ), b >>= 1 ) ret = mul ( ret, b & 1 ? a : 1 );
	return ret;
}

inline void init () {
	fac[0] = 1;
	for ( int i = 1; i <= n; ++ i ) fac[i] = mul ( i, fac[i - 1] );
	ifac[n] = qkpow ( fac[n], MOD - 2 );
	for ( int i = n - 1; ~ i; -- i ) ifac[i] = mul ( i + 1, ifac[i + 1] );
}

inline int comb ( const int n, const int m ) {
	return n < m ? 0 : mul ( fac[n], mul ( ifac[m], ifac[n - m] ) );
}

int main () {
	scanf ( "%s %s", A + 1, B + 1 );
	n = strlen ( A + 1 ), init ();
	for ( int i = 1; i <= n; ++ i ) {
		if ( A[i] ^ '0' && B[i] ^ '0' ) ++ bal;
		else if ( A[i] > B[i] ) ++ sur;
	}
	f[0][0] = 1;
	for ( int i = 0; i <= bal; ++ i ) {
		for ( int j = 0, cur; j <= sur; ++ j ) {
			if ( ! ( cur = f[i][j] ) ) continue;
			addeq ( f[i + 1][j], mul ( cur, mul ( i + 1, j ) ) );
			addeq ( f[i][j + 1], mul ( cur, mul ( j + 1, j + 1 ) ) );
		}
	}
	int ans = 0;
	for ( int i = 0; i <= bal; ++ i ) {
		int fre = bal - i;
		int self = mul ( mul ( fac[fre], fac[fre] ),
			mul ( comb ( bal + sur, fre ), comb ( bal, fre ) ) );
		addeq ( ans, mul ( f[i][sur], self ) );
	}
	printf ( "%d\n", ans );
	return 0;
}