[CF1400G]Mercenaries

题目

点这里看题目。

分析

这......看到 \(m\) 这么小,然后看到条件这么奇葩,显然是容斥计算。

但是先不慌,我们先考虑在没有任何限制的时候该怎么计算。

考虑枚举选的人数 \(s\) ,然后找出哪些佣兵在选的人数为 \(s\) 的时候可以被选,设为 \(a_s\) 。那么总的方案数就是:

\[\sum_{s=1}^n \binom{a_s}{s} \]

会不会计算方案数直接决定了是你切这道题还是这道题切你。

然后不难想到套上容斥。设选的人数为 \(s\) 时,会影响到选取的限制的集合为 \(R_s\) 。顺手定义一个 \(cnt(R)\) ,表示集合 \(R\) 中的限制会影响到的佣兵的数量。

于是不难看出上面的式子可以扩展成为:

\[\sum_{s=1}^n \sum_{T\subset R_s} (-1)^{|T|} \binom{a_s-cnt(T)}{s-cnt(T)} \]

就相当于钦定了 \(T\) 会影响到的人不选。

然后注意到,容斥中枚举的实际上就是 \(cnt(T)\) 。因此改写式子:

\[\sum_{s=1}^n \sum_{c=0}^{2m} \binom{a_s-c}{s-c}\sum_{T\subset R_s} [cnt(T)=c](-1)^{|T|} \]

继续发现最后一层的式子的值仅与 \(R_s\)\(c\) 相关,且 \(R_s\) 可压缩、\(c\) 还很小。因此考虑预处理 \(f\)

\[f_{c,R}=\sum_{T\subset 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)\)

小结:

  1. 计算方案数的基础方法。我感觉这就是本题的难点。本题的限制很特殊,因此可以考虑通过枚举变量来化开限制。
  2. 容斥思想和预处理系数。感觉很套路
  3. 高维前缀和的运用。

代码

#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( '\n' );
    return 0;
}
posted @ 2020-09-23 21:13  crashed  阅读(152)  评论(0编辑  收藏  举报