「6月雅礼集训 2017 Day2」B

【题目大意】

求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;
}
View Code

多贴几份考试写的各种暴力吧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\n", 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\n", f[i][j]); 
        }
        swap(pre, cur);
    }
    cout << f[pre][n] << ',';

    return 0;
}
View Code

把含有n-...的项换成不含n的等价表示就有70分了。。

我是考试的时候打了个1w的表发现100K,压了一半,套这个做法。。50(被评测机卡)

posted @ 2017-06-18 18:44  Galaxies  阅读(246)  评论(0编辑  收藏  举报