【题目大意】
求n*n的棋盘,每行每列都有2个黑格子的方案数。
n<=10^7
【题解】
zzq的做法好神奇啊
行列建点,二分图
左边有i个点,右边有j个点的方案数 f[i,j]
左边有i个点,2个已经有1个度,右边有j个点的方案数 g[i,j]
g[i,j] = f[i-2,j-1]*j + g[j,i-2]*P(j,2)
f[i,j] = g[i,j-1] * C(i,2) = g[j,i-1] * C(j,2)
g[j,i-2] = g[i-1,j-1] * C(i-1,2) / C(j,2)
g[i,j] = g[i-2,j-2] * C(i-2,2) * j + g[i-1, j-1] * C(i-1,2) / C(j,2) * P(j,2)
g[i,j] = g[i-2,j-2] * C(i-2,2) * j + g[i-1, j-1] * C(i-1,2) * 2
g[x] = g[x, x-1]
g[x] = g[x-2] * C(x-2, 2) * (x-1) + g[x-1] * C(x-1, 2) * 2
ans = sigma (C(x,2) * g[x])
Q: 为什么从f转移到g,只乘了右边选择的部分,不管左边;从g转移到f,只乘了左边的部分,不管右边?
A: g实际的意义是我钦定左边最后两个度数为1的方案数,f实际的意义是我钦定右边最后1或2个是我最后填进来。 我从g到f,目的是消去两个度为1的点,重点是消去,我要优先考虑消除哪两个点,按照我g的定义,每两个度数为1的点作为选择,都有这么多方案,所以要乘组合数;从f到g,目的是制造两个度为1的点,按照f的定义,每1或2个点作为选择,都有这么多方案,所以要乘j和后面的那个组合数。意义在于,我要有一个顺序来填数,不能xjb填,这样会统计重复方案,我们用钦(ying)点来避免这样的问题。

# include <stdio.h> # include <string.h> # include <iostream> # include <algorithm> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef long double ld; # define RG register # define ST static const int M = 1e7 + 10; const int mod = 998244353; int n, g[M], ans; inline int C(int n, int k = 2) { return (ll)n * (n-1) / 2 % mod; } int main() { // freopen("B.in", "r", stdin); // freopen("B.out", "w", stdout); cin >> n; g[1] = 0, g[2] = 1; for (int i=3; i<=n; ++i) { g[i] = 1ll * C(i-2) * (i-1) % mod * g[i-2] % mod + 2ll * C(i-1) * g[i-1] % mod; if(g[i] >= mod) g[i] -= mod; } for (int i=1; i<=n; ++i) { ans += 1ll * C(i) * g[i] % mod; if(ans >= mod) ans -= mod; } cout << ans; return 0; }
多贴几份考试写的各种暴力吧qwq
O(n^2)求单点暴力

# include <stdio.h> # include <string.h> # include <iostream> # include <algorithm> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef long double ld; # define RG register # define ST static const int M = 1e4 + 10, N = 1e5 + 10; const int mod = 998244353; int n, f[2][M]; // row i, line2: j line1: k line0: l inline int C(int n, int k) { if(k == 1) return n; if(k == 2) return (ll)n * (n-1) / 2 % mod; } int main() { // freopen("B.in", "r", stdin); // freopen("B.out", "w", stdout); int pre = 0, cur = 1; cin >> n; for (int j=0; j<=n; ++j) f[pre][j] = 0; f[pre][0] = 1; for (int i=1; i<=n; ++i) { for (int j=0; j<=i; ++j) { f[cur][j] = 0; // i*2 = j*2 + k => k = i*2 - j*2 int k = i*2 - j*2, l = n-j-k; // printf("%d line2: %d line1: %d line0: %d ", i, j, k, l); if(l < 0) continue; if(j >= 2) { f[cur][j] += 1ll * C(k+2, 2) * f[pre][j-2] % mod; if(f[cur][j] >= mod) f[cur][j] -= mod; } if(k >= 2) { f[cur][j] += 1ll * C(l+2, 2) * f[pre][j] % mod; if(f[cur][j] >= mod) f[cur][j] -= mod; } if(j && k) { f[cur][j] += 1ll * C(k, 1) * C(l+1, 1) * f[pre][j-1] % mod; // 10w: need mod again if(f[cur][j] >= mod) f[cur][j] -= mod; } // printf("F = %d ", f[i][j]); } swap(pre, cur); } cout << f[pre][n] << ','; return 0; }
把含有n-...的项换成不含n的等价表示就有70分了。。
我是考试的时候打了个1w的表发现100K,压了一半,套这个做法。。50(被评测机卡)