G. Mercenaries (组合数,容斥,数学)

题目:传送门

题意

有 n 个区间,你可以选 i 这个区间的条件是,你选的所有区间的总数介于 [ li, ri ] 之间,有 m 对限制条件,每个限制条件输入两个数 u, v,表示区间 u 和 v 不能同时被选上。问你有多少种不同的满足条件的取法。输出答案对 998244353 取模, 1 <= n <= 3e5; 0 <= m <= min(n * (n - 1) / 2,  20);

思路

若没有限制条件,那么我们可以用 cnt[i] 表示取 i 个区间,可以被选的区间数。那么取 i 个区间的方案数就是 C(cnt[i], i);

对于每个区间 [l, r],当取的点数在区间 [l, r] 内时,这个区间可以被选上,所以,cnt[ l ]++, cnt[ r + 1] --,最后,让 cnt[i] 累加起来,就得到真正的 cnt[i] 了。

现在,考虑加入了限制条件,由于最多只有 20 对限制条件,可以想到用状压暴力枚举所有状态。

我们可以先算出所有不符合题意的情况,最后用全集减去不符的就是最终的答案。

那么我们可以暴力枚举所有情况,当二进制的第 i 位为 1 时,表示选上第 i 个区间的两个数,然后,去重,可以得到现在已经选上的数的个数。

详情看代码注释。

#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define mem(i, j) memset(i, j, sizeof(i))
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define INF 0x3f3f3f3f
#define inf LLONG_MAX
#define PI acos(-1)
#define fir first
#define sec second
#define lb(x) ((x) & (-(x)))
#define dbg(x) cout<<#x<<" = "<<x<<endl;
using namespace std;

const int N = 1e6 + 5;

const LL mod = 998244353;

int n, m;

struct note {

    int l, r;

}a[N];

int u[N], v[N], cnt[N];

LL sum[50][N];

LL fac[N], ifac[N];

LL ksm(LL a, LL b) {

    LL res = 1LL;

    while(b) {

        if(b & 1) res = res * a % mod;

        a = a * a % mod; b >>= 1;

    }

    return res;

}

void init() { /// 预处理阶乘

    fac[0] = 1LL;  ifac[0] = 1LL;

    rep(i, 1, 300000) fac[i] = 1LL * i * fac[i - 1] % mod;

    ifac[300000] = ksm(fac[300000], mod - 2);

    dep(i, 0, 299999) ifac[i] = 1LL * (i + 1) * ifac[i + 1] % mod;

}

LL C(int n, int m) { /// 组合数

    if(n < m || m < 0) return 0;

    return fac[n] * ifac[m] % mod * ifac[n - m] % mod;

}

set < int > Q;

void solve() {

    scanf("%d %d", &n, &m);

    rep(i, 1, n) {

        scanf("%d %d", &a[i].l, &a[i].r);

        cnt[a[i].l]++;

        cnt[a[i].r + 1]--;

    }

    rep(i, 1, n) cnt[i] += cnt[i - 1]; /// 累加起来

    rep(i, 0, m - 1) {

        scanf("%d %d", &u[i], &v[i]);

    }
    
    
    /// 预处理, sum[i][j] 表示选了 j 个区间,其中有 i 个是限制条件上的区间的方案书。
    rep(i, 0, 2 * m) {

        rep(j, 1, n) {

            sum[i][j] = (sum[i][j - 1] + C(cnt[j] - i, j - i)) % mod;

        }

    }

    LL ans = 0LL;

    rep(i, 0, (1 << m) - 1) { /// 暴力枚举

        int l = 1, r = n;

        bool flag = 0; ///容斥,选了0个限制条件 - 选了1个限制条件 + 选了 2 个限制条件 ... ,也就是 i 的二进制位有偶数个1就加上,否则就减去

        Q.clear(); /// set自动去重

        rep(j, 0, m - 1) {

            if((i >> j) & 1) {

                Q.insert(u[j]);

                Q.insert(v[j]);
                
                /// 求限制条件区间的交集
                
                l = max(l, a[u[j]].l);

                l = max(l, a[v[j]].l);

                r = min(r, a[u[j]].r);

                r = min(r, a[v[j]].r);

                flag = !flag;

            }

        }

        int up = Q.size(); /// 有 up 个区间是在限制条件上的

        LL tmp = 0;

        if(l <= r) tmp = (sum[up][r] - sum[up][l - 1] + mod) % mod; ///取的总区间的个数介于 交集 l ~ r 之间,才能保证当前状态所有有限制条件的区间都能被选上

        if(!flag) ans = (ans + tmp) % mod;

        else ans = (ans - tmp + mod) % mod;

    }

    printf("%lld\n", ans);

}


int main() {

    init();

//    int _; scanf("%d", &_);
//    while(_--) solve();

    solve();

    return 0;
}

 

posted on 2020-09-10 00:23  Willems  阅读(186)  评论(0编辑  收藏  举报

导航