AtCoder - 1983 BBQ Hard

本文探讨了组合数学在算法竞赛中的应用,通过将原问题转化为求从一个点到另一个点的所有路径数量,利用组合数学中的组合公式进行计算。文章提供了一种高效的算法实现,包括预处理阶乘和逆元,以及使用动态规划加速计算。
摘要由CSDN通过智能技术生成
题意

给你n个数对 ( a i , b i ) (a_i,b_i) (ai,bi),代表第i个包裹中两种物品分别有 a i , b i a_i,b_i ai,bi个。第i个包裹中有第i种针。
每次取两个包裹 i , j i,j i,j。那么一共有 a i + a j a_i+a_j ai+aj种物品1,有 b i + b j b_i+b_j bi+bj种物品2。将所有物品排成一列,串在i,j两个针上,形成一个烤串。询问有多少种靠串的类型,不同的针视为不同类型。

思路

也就是要求取
∑ i = 1 n ∑ j = 1 i − 1 ( a i + a j + b i + b j ) ! ( a i + a j ) ! ( b i + b j ) ! = ∑ i = 1 n ∑ j = 1 i − 1 C a i + a j + b i + b j a i + a j \sum_{i=1}^{n}\sum_{j=1}^{i-1}\frac{(a_i+a_j+b_i+b_j)!}{(a_i+a_j)!(b_i+b_j)!}=\sum_{i=1}^{n}\sum_{j=1}^{i-1}C_{a_i+a_j+b_i+b_j}^{a_i+a_j} i=1nj=1i1(ai+aj)!(bi+bj)!(ai+aj+bi+bj)!=i=1nj=1i1Cai+aj+bi+bjai+aj
考虑两个问题:

  1. 给定n个0和m个1,问构成的01串的种类有多少种。
  2. 以(0,0)为起点,只能向上或向右走,问走到(n,m)的走法一共有多少种。

两个问题本质上是等价的。向右走可以等价成选一个0,向上可以等价为选一个1。两个问题的答案均是 C n + m n C_{n+m}^{n} Cn+mn
原题的问题即是问题1,可以转化为第二种问题。即,选 ( a i , b i ) (a_i,b_i) (ai,bi) ( a j , b j ) (a_j,b_j) (aj,bj)可以看成二维平面上 ( − a i , − b i ) (-a_i,-b_i) (ai,bi)走到 ( a j , b j ) (a_j,b_j) (aj,bj)的路径数。
这样可以令所有 f [ − a i ] [ − b i ] + + f[-a_i][-b_i]++ f[ai][bi]++(初始化)。最后答案就是 ∑ f [ a i ] [ b i ] \sum f[a_i][b_i] f[ai][bi]。要去除自己走到自己的情况,即减去 ∑ C a i ∗ 2 + a j ∗ 2 a i + a j \sum C_{a_i*2+a_j*2}^{a_i+a_j} Cai2+aj2ai+aj。然后每对点算了两次,还应该/2。

代码
//
// Created by yjq on 2019/10/23.
//

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define ld long double
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

const int N = 2010;
const int maxn = 2e5 + 10;
const ll mod = 1e9 + 7;
ll f[N * 3][N * 3], a[maxn], b[maxn], jie[maxn], ni[maxn];

ll pow_mod(ll a, ll b, ll m) {
    ll ans = 1;
    while (b) {
        if (b & 1)ans = ans * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return ans;
}

int main() {
    __;
    jie[0] = 1;
    for (int i = 1; i < maxn; ++i) {
        jie[i] = jie[i - 1] * (ll) i;
        jie[i] %= mod;
    }
    ni[maxn - 10] = pow_mod(jie[maxn - 10], mod - 2, mod);
    for (int i = maxn - 11; i >= 0; --i) {
        ni[i] = ni[i + 1] * (ll) (i + 1) % mod;
    }
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i] >> b[i];
        f[N - a[i]][N - b[i]]++;
    }
    for (int i = 1; i <= N * 2; ++i) {
        for (int j = 1; j <= N * 2; ++j) {
            f[i][j] += f[i - 1][j] + f[i][j - 1];
            f[i][j] %= mod;
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans += f[a[i] + N][b[i] + N];
        ans = (ans - ((jie[a[i] * 2 + b[i] * 2] * ni[a[i] * 2]) % mod * ni[b[i] * 2]) % mod + mod + mod) % mod;
    }
    cout << (ans * ni[2]) % mod << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值