题目
点这里看题目。
分析
这......看到 (m) 这么小,然后看到条件这么奇葩,显然是容斥计算。
但是先不慌,我们先考虑在没有任何限制的时候该怎么计算。
考虑枚举选的人数 (s) ,然后找出哪些佣兵在选的人数为 (s) 的时候可以被选,设为 (a_s) 。那么总的方案数就是:
[sum_{s=1}^n inom{a_s}{s}
]
会不会计算方案数直接决定了是你切这道题还是这道题切你。
然后不难想到套上容斥。设选的人数为 (s) 时,会影响到选取的限制的集合为 (R_s) 。顺手定义一个 (cnt(R)) ,表示集合 (R) 中的限制会影响到的佣兵的数量。
于是不难看出上面的式子可以扩展成为:
[sum_{s=1}^n sum_{Tsubset R_s} (-1)^{|T|} inom{a_s-cnt(T)}{s-cnt(T)}
]
就相当于钦定了 (T) 会影响到的人不选。
然后注意到,容斥中枚举的实际上就是 (cnt(T)) 。因此改写式子:
[sum_{s=1}^n sum_{c=0}^{2m} inom{a_s-c}{s-c}sum_{Tsubset R_s} [cnt(T)=c](-1)^{|T|}
]
继续发现最后一层的式子的值仅与 (R_s) 和 (c) 相关,且 (R_s) 可压缩、(c) 还很小。因此考虑预处理 (f) 。
[f_{c,R}=sum_{Tsubset R}[cnt(T)=c](-1)^{|T|}
]
不难想到设 (g_{c,T}=[cnt(T)=c](-1)^{|T|}) 。于是 (f_{c,R}) 就是 (g_{c,T}) 的子集前缀和(或者叫高维前缀和)。这个可以单层 (O(m2^m)) ,总时间 (O(m^22^m)) 地处理出来。
然后用扫描线处理一下可选的佣兵的集合和数量,就可以得到每个 (s) 对应的 (R_s) 和 (a_s) ,最后计算答案。时间复杂度是 (O(m^22^m+nm)) 。
小结:
- 计算方案数的基础方法。
我感觉这就是本题的难点。本题的限制很特殊,因此可以考虑通过枚举变量来化开限制。 - 容斥思想和预处理系数。
感觉很套路 - 高维前缀和的运用。
代码
#include <cstdio>
#include <vector>
#include <utility>
using namespace std;
typedef long long LL;
typedef pair<int, int> Ristr;
const int mod = 998244353;
const int MAXN = 3e5 + 5, MAXM = 25, MAXS = ( 1 << 20 ) + 5;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
vector<int> ad[MAXN], del[MAXN];
int f[MAXM << 1][MAXS];
int fr[MAXM], to[MAXM];
int fac[MAXN], ifac[MAXN];
int id[MAXN];
int N, M, cnt;
bool app[MAXM << 1], in[MAXN];
int Qkpow( int, int );
int Lowbit( const int x ) { return x & ( -x ); }
int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
int Sub( int x, int v ) { return x < v ? x + mod - v : x - v; }
int Mul( LL x, int v ) { x *= v; if( x >= mod ) x %= mod; return x; }
int Add( int x, int v ) { return x + v >= mod ? x + v - mod : x + v; }
int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }
int Count( const int S )
{
for( int i = 1 ; i <= M << 1 ; i ++ ) app[i] = false;
for( int i = 1 ; i <= M ; i ++ )
if( S & ( 1 << i - 1 ) )
app[id[fr[i]]] = true, app[id[to[i]]] = true;
int tot = 0;
for( int i = 1 ; i <= M << 1 ; i ++ )
tot += app[i];
return tot;
}
int Qkpow( int base, int indx )
{
int ret = 1;
while( indx )
{
if( indx & 1 ) ret = Mul( ret, base );
base = Mul( base, base ), indx >>= 1;
}
return ret;
}
void Init()
{
int upper = 1 << M, coe, tot;
for( int S = 0 ; S < upper ; S ++ )
{
coe = 1, tot = Count( S );
for( int i = 1 ; i <= M ; i ++ )
if( S & ( 1 << i - 1 ) )
coe = mod - coe;
f[tot][S] = Add( f[tot][S], coe );
}
for( int i = 0 ; i <= M << 1 ; i ++ )
for( int k = 1 ; k < upper ; k <<= 1 )
for( int S = k ; S < upper ; S ++ )
if( S & k )
f[i][S] = Add( f[i][S], f[i][S ^ k] );
fac[0] = 1; for( int i = 1 ; i <= N ; i ++ ) fac[i] = Mul( fac[i - 1], i );
ifac[N] = Inv( fac[N] ); for( int i = N - 1 ; ~ i ; i -- ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}
int main()
{
read( N ), read( M );
for( int i = 1, l, r ; i <= N ; i ++ )
read( l ), read( r ), ad[l].push_back( i ), del[r + 1].push_back( i );
for( int i = 1 ; i <= M ; i ++ )
read( fr[i] ), read( to[i] ), id[fr[i]] = ++ cnt, id[to[i]] = ++ cnt;
Init();
int cur = 0, ans = 0, all = 0;
for( int s = 1 ; s <= N ; s ++ )
{
cur = 0;
for( int i = 0 ; i < ( int ) ad[s].size() ; i ++ ) in[ad[s][i]] = true, all ++;
for( int i = 0 ; i < ( int ) del[s].size() ; i ++ ) in[del[s][i]] = false, all --;
for( int i = 1 ; i <= M ; i ++ ) if( in[fr[i]] && in[to[i]] ) cur |= 1 << i - 1;
for( int c = 0 ; c <= M << 1 ; c ++ )
ans = Add( ans, Mul( C( all - c, s - c ), f[c][cur] ) );
}
write( ans ), putchar( '
' );
return 0;
}