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. Go 的定时任务模块 Cron 使用

    前言 新项目是Golang作为开发语言, 遇到了些新的坑, 也学到了新的知识, 收获颇丰 本章介绍在Go中使用Cron定时任务模块来实现逻辑 正文 在项目中, 我们往往需要定时执行一些逻辑, 举个例子 ...

  2. SpringBoot魔法堂:@MatrixVariable参数注解使用详解

    前言 RFC3986定义URI的路径(Path)中可包含name-value片段,扩充了以往仅能通过查询字符串(Query String)设置可选参数的囧境. 假如现在需要设计一个用于"搜索 ...

  3. 如何利用Intellij Idea搭建python编译运行环境 (转)

    首先进入Intellij Idea的官方网站:点击打开链接 点击download,选择旗舰版进行下载.网上的破解教程很多,也可以注册一个学生账号拿到一年的免费试用权. 安装过程不再细说,第一次打开选择 ...

  4. disfunc绕过

    绕过DisFunc的常见小技巧 解析webshell命令不能执行时的三大情况 一是 php.ini 中用 disable_functions 指示器禁用了 system().exec() 等等这类命令 ...

  5. Flask+pin

    Flask+SSTI的新火花 记一次buu刷题记和回顾祥云杯被虐出屎的经历.题目:[GYCTF2020]FlaskApp 一 题目初见 朴实无华的页面,一个base64的小程序页面 看到有提示. 我就 ...

  6. 使用modify修改内表

    modify修改内表,有这样一种方式,MODIFY TABLE itab FROM wa [TRANSPORTING ..]. 然后这里的内表itab是有条件的,这个itab必须要有table key ...

  7. Java编程开发之浅析Java引用机制

    对于一个Java的对象而言,存储主要分为两种,一种是内存堆(Heap),内存堆是无序的,主要用来存放创建的Java对象:一种是内存栈(Stack),主要用来存放Java引用,然后在管理过程使用Java ...

  8. Gulp4.0入门和实战

    gulp4.0入门和实战 我最近遇到需要优化web的性能的任务,然后就捣鼓了一些对资源文件优化压缩的方案.由于之前的项目中有使用到gulp,所以在需要处理的web项目中也优先使用这个技术. 先聊聊gu ...

  9. drf认证、节流、权限、版本

    Django rest framework 认证: 作用:验证用户是否登录 在视图类中写上authentication_classes = [ ],这是一个列表 需要实现 authenticate() ...

  10. 聊一聊:Service层你觉得有用吗?

    前段日子在社群(点击加入)里看到有人讨论关于Service层接口的问题,DD也经常碰到周围的新人有问过一些类似的问题:一定要写个Service层的接口吗?Service层的接口到底用做什么用的呢?好像 ...