1. 程式人生 > 實用技巧 >G. Mercenaries (組合數,容斥,數學)

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; }