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; }
一步一步,永不停息