CF 1400G.Mercenaries

题意:

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

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

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

\(1\le n\le 3\times 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 sub\mid] = 1\),然后我们通过高维前缀和(\(SOS\ DP\))就可以在\(O(m^2\cdot 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 "\n"
#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;
}

CF 1400G.Mercenaries 题解【SOSDP 组合数学】的更多相关文章

  1. CF#581 (div2)题解

    CF#581 题解 A BowWow and the Timetable 如果不是4幂次方直接看位数除以二向上取整,否则再减一 #include<iostream> #include< ...

  2. CF Round#240题解

    第一次参加CF的比赛,MSK19.30,四个小时的时差真心累,第一次CODE到这么夜-- 一开始做了A,C两题,后来做B题的时候我体力和精神集中度就很低了,导致一直WA在4-- 今天起床后再刷B,终于 ...

  3. CF Round #808 题解 (Div. 2 ABCD)

    后面题太难搞不动 . ABCD 的题解写的好水啊,感觉在写闲话,,, A 若 \(\forall i, a_1\mid a_i\),则可以 . 注意判 \(0\) 的情况 . 提交记录 . B 显而易 ...

  4. [题解](组合数学/gcd)luogu_P3166数三角形

    首先转化为ans=所有的组合方式 - 在同一水平/竖直线上 - 在同一斜线上 主要考虑在同一斜线上的情况 首先想到枚举斜率然后在坐标系内平移,以(0,0)为起点,每条线上的点数应该是gcd(x,y)比 ...

  5. CF 1178E Archaeology 题解

    题面 这道题竟然是E?还是洛谷中的黑题? wow~!! 于是就做了一下: 然后一下就A了:(这并不代表想的容易,而是写的容易) 这道题就是骗人的!! 什么manacher,什么回文自动机,去靠一边站着 ...

  6. [CQOI2011]放棋子 题解(dp+组合数学)

    Description Input 输入第一行为两个整数n, m, c,即行数.列数和棋子的颜色数. 第二行包含c个正整数,即每个颜色的棋子数. 所有颜色的棋子总数保证不超过nm. N,M<=3 ...

  7. 【NOIP2016】组合数问题 题解(组合数学+递推)

    题目链接 题目大意:给定$n,m,k$,求满足$k|C_i^j$的$C_i^j$的个数.$(0\leq i\leq n,1\leq j\leq \min(i,m))$. --------------- ...

  8. 【FZYZOJ】无向图的联通图个数 题解(组合数学)

    题目大意:求无向图的连通图个数.由于个数可能很大,只需要求出结果$mod1000000009$的值.$n\leq 1000$ ------------------------- 对于一个含有$n$个结 ...

  9. CF 1394 简要题解

    最近都会做一些 \(\rm Div1\) 套题中 \(3000\) 分以下的题目. A 直接枚举贪心即可. B 首先不难发现总共可能的 \(c\) 序列只有 \(k!\) 种,很明显要暴力枚举所有情况 ...

随机推荐

  1. TeamView WaitforConnectFailed错误原因

    更新到最新版本并重启如下服务 检查TCP IPV4是否选中

  2. ORA-39700: database must be opened with UPGRADE option【转】

    1. 错误 数据库升级后(从11.2.0.1升级到11.2.0.4)启动报错 SQL> startup ORACLE instance started.   Total System Globa ...

  3. Scrapy使用RabbitMQ做任务队列

    前言 一个月没更博客了,这个月也搞了不少东西,但是公司对保密性要求挺高,很多东西都没有办法写出来 想来想去,还是写一篇最近写Scrapy中遇到的跳转问题 如果你的业务需求是遇到301/302/303跳 ...

  4. 借助window.performance实现基本的前端基础性能监控日志

    借助window.performance实现基本的前端基础性能监控日志并二次重写console方法方便日常前端console日志的调试 npm install sn-console

  5. 【Flutter】容器类组件之装饰容器

    前言 DecoratedBox可以在其子组件绘制前后绘制一些装饰,例如背景,边框,渐变等. 接口描述 const DecoratedBox({ Key key, // 代表要绘制的装饰 @requir ...

  6. logback为不同的包或类指定输出日志文件

    对日志分割的常见需求是,需要按不同的等级进行输出,这个的配置方式类似如下,在appender节点内添加内容 <appender name="FILE-INFO" class= ...

  7. 【EXP】比较大的dmp文件导入的时候可以将界面关掉

    有一个需求,将一个dmp文件导入到数据库中,但是这个crt的回话有timeout,3分钟,所以一到三分钟就掉线,导入就失败了,这让人很头疼,关闭界面,imp也就终止了 最后想到了几招,分享一下: 1. ...

  8. jQuery 点击当前展开其他隐藏

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  9. Linux网卡没有eth0显示ens33原因以及解决办法

    原因 首先说明下eth0与ens33的关系: 目前的主流网卡为使用以太网络协定所开发出来的以太网卡 (Ethernet),因此我们 Linux 就称呼这种网络接口为 ethN (N 为数字). 举例来 ...

  10. 深度漫谈数据系统架构——Lambda architecture

    https://mp.weixin.qq.com/s/whmhm2yzug2WVdH3dTq8hg