zoukankan      html  css  js  c++  java
  • CF 1400G.Mercenaries 题解【SOSDP 组合数学】

    CF 1400G.Mercenaries

    题意:

    (n)个佣兵,问雇佣至少一名雇佣兵且满足下述条件的方案数

    • 如果雇佣第(i)个佣兵必须要求最终雇佣的总人数(x)满足(l_ile xle r_i)

    • (m)对佣兵不能同时选

    (1le nle 3 imes 10^5,0 le m le min(20, dfrac{n(n-1)}{2}))

    题解:

    首先对于第一个限制,我们考虑枚举最终雇佣的总人数来做

    对于第二个限制,可以把这些不能同时选的点连边,不考虑单独一个点的块,那么对于每个连通块,能选的方案必然是其中的一个独立集,单独考虑每一个连通块,由于(m)很小,所以最大的连通块里的点数不会超过(m+1)

    我们定义(h[i][x][k])表示考虑第(i)个连通块,最终雇佣人数为(x)的情况下,雇佣连通块(i)中的(k)个人的合法方案数

    定义(f[i][msk][k])表示考第(i)个联通块中,在(msk)这个集合中选雇佣兵,选(k)个的合法方案

    (S_{i,x})为第(i)个连通块中,最终选取人数为(x)且只考虑第一个限制的情况下可选的雇佣兵的集合

    那么可以发现(h[i][x][k]=f[i][S_{i,x}][k])

    考虑如何计算(f[i][msk][k]),我们可以先(2^m)枚举这个集合中的所有子集(sub),如果某个子集(sub)合法(即这个子集是一个独立集),那么我们令(f[i][sub][mid submid] = 1),然后我们通过高维前缀和((SOS DP)就可以在(O(m^2cdot 2^m))的时间内处理出所有的(f[i][msk][k])

    最终我们枚举雇佣总人数,然后用分组背包处理出这些大于(1)的连通块的选择方案,然后剩下的那些大小为(1)的连通块可以直接用组合数来算

    view code
    #pragma GCC optimize("O3")
    #pragma GCC optimize("Ofast,no-stack-protector")
    #include<bits/stdc++.h>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define endl "
    "
    #define LL long long int
    #define vi vector<int>
    #define vl vector<LL>
    #define all(V) V.begin(),V.end()
    #define sci(x) scanf("%d",&x)
    #define scl(x) scanf("%I64d",&x)
    #define scs(x) scanf("%s",s)
    #define pii pair<int,int>
    #define pll pair<LL,LL>
    #ifndef ONLINE_JUDGE
    #define cout cerr
    #endif
    #define cmax(a,b) ((a) = (a) > (b) ? (a) : (b))
    #define cmin(a,b) ((a) = (a) < (b) ? (a) : (b))
    #define debug(x)  cerr << #x << " = " << x << endl
    function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
    template <typename T> vector<T>& operator << (vector<T> &__container, T x){ __container.push_back(x); return __container; }
    template <typename T> ostream& operator << (ostream &out, vector<T> &__container){ for(T _ : __container) out << _ << ' '; return out; }
    const int MAXN = 3e5+7;
    const int MOD  = 998244353;
    int n, m, root[MAXN], sz[MAXN], l[MAXN], r[MAXN], cnt[MAXN];
    int fac[MAXN], inv[MAXN], rfac[MAXN];
    vector<vi> comp;
    vector<vector<vi> > f;
    int compID;
    map<int,int> msk;
    set<pair<int,int> > S;
    int findx(int x){ return x == root[x] ? x : root[x] = findx(root[x]); }
    int C(int n, int m){ return n < m ? 0 : 1ll * fac[n] * rfac[m] % MOD * rfac[n-m] % MOD; }
    // preprocess with sosdp
    void preprocess(int id){
        f[id] = vector<vi>(comp[id].size()+1,vi(1<<comp[id].size(),0));
        auto &dp = f[id];
        for(int i = 0; i < (1<<comp[id].size()); i++){
            bool ok = true;
            for(int bit1 = 0; bit1 < comp[id].size(); bit1++){
                if(!(i>>bit1&1)) continue;
                for(int bit2 = bit1 + 1; bit2 < comp[id].size(); bit2++){
                    if(!(i>>bit2&1)) continue;
                    int x = comp[id][bit1], y = comp[id][bit2];
                    if(S.count(make_pair(min(x,y),max(x,y)))) ok = false;
                }
            }
            if(ok) dp[__builtin_popcount(i)][i] = 1;
        }
        for(int k = 0; k <= (int)comp[id].size(); k++)
            for(int i = 0; i < (int)comp[id].size(); i++) for(int msk = 0; msk < (1<<comp[id].size()); msk++)
                if(msk>>i&1) dp[k][msk] += dp[k][msk^(1<<i)];
    }
    
    void solve(){
        sci(n); sci(m);
        for(int i = 1; i <= n; i++) sci(l[i]), sci(r[i]);
        for(int i = 1; i <= n; i++) root[i] = i, sz[i] = 1;
        for(int i = 1; i <= m; i++){
            int u, v; sci(u); sci(v);
            S.insert(make_pair(min(u,v),max(u,v)));
            if(findx(u)==findx(v)) continue;
            int fu = findx(u), fv = findx(v);
            root[fu] = fv; sz[fv] += sz[fu];
        }
        for(int i = 1; i <= n; i++){
            if(sz[findx(i)]==1) continue;
            int fx = findx(i);
            if(!msk.count(fx)) msk[fx] = compID++, comp << vi();
            comp[msk[fx]] << i;
        }
        f.resize(compID);
        for(int i = 0; i < compID; i++) preprocess(i);
        for(int i = 1; i <= n; i++) if(sz[findx(i)]==1) cnt[l[i]]++, cnt[r[i]+1]--;
        for(int i = 1; i <= n; i++) cnt[i] += cnt[i-1];
        int ret = 0, tot = 0;
        for(int i = 1; i <= n; i++) if(findx(i)==i and sz[i]!=1) tot += sz[i];
        for(int i = 1; i <= n; i++){                    // 枚举总数
            int top = min(tot,i);
            vector<vi> g(2,vi(top+1,0));
            int tag = 0;
            g[0][0] = 1;
            for(int j = 0; j < compID; j++){
                tag ^= 1;
                fill(all(g[tag]),0);
                int msk = 0;
                for(int k = 0; k < (int)comp[j].size(); k++) if(l[comp[j][k]]<=i and i<=r[comp[j][k]]) msk |= (1 << k);       // 找出当前集合中符合条件的
                for(int k = 0; k <= min(top,__builtin_popcount(msk)); k++)          // 枚举当前集合中选的数量
                    for(int p = k; p <= top; p++) g[tag][p] = (g[tag][p] + 1ll * g[tag^1][p-k] * f[j][k][msk]) % MOD;
            }
            for(int j = 0; j <= top; j++) ret = (ret + 1ll * g[tag][j] * C(cnt[i],i-j)) % MOD;
        }
        cout << ret << endl;
    }
    int main(){
        #ifndef ONLINE_JUDGE
        freopen("Local.in","r",stdin);
        freopen("ans.out","w",stdout);
        #endif
        fac[0] = rfac[0] = inv[1] = 1;
        for(int i = 1; i < MAXN; i++) fac[i] = 1ll * fac[i-1] * i % MOD;
        for(int i = 2; i < MAXN; i++) inv[i] = 1ll * (MOD - MOD/i) * inv[MOD%i] % MOD;
        for(int i = 1; i < MAXN; i++) rfac[i] = 1ll * rfac[i-1] * inv[i] % MOD;
        solve();
        return 0;
    }
    
  • 相关阅读:
    python基础之包、模块、命名空间和作用域
    python基础之函数式编程
    python基础之文件操作
    python基础之psutil模块和发邮件(smtplib和yagmail)
    【面试题21】包含min函数的栈
    【面试题20】顺时针打印矩阵
    【面试题19】二叉树的镜像
    【面试题18】树的子结构
    【面试题17】合并两个排序的链表
    【面试题16】反转链表
  • 原文地址:https://www.cnblogs.com/kikokiko/p/13574977.html
Copyright © 2011-2022 走看看