「ABC215H」Cabbage Master
题目
点这里看题目。
分析
显然可以构建出二分图的模型:将菜田放在左部,将订单放在右部,那么成为 Cabbage Master 的条件就是存在一个右部点被覆盖完的完美匹配。
那么很容易想到使用 Hall 定理。我们可以枚举右部的一个点集,并且取出右部中每个点的邻接点的并集,检查邻接点的总量是否足够。
形式化地,设左部点点集为 \(L\),右部点点集为 \(R\),对于 \(x\in R\),它的邻接点集为 \(S_x\)。设 \(f(S)=\sum_{x\in S}A_x\)。那么存在右部点被覆盖完的完美匹配等价于:
由于 \(n\) 很小,我们自然想到将枚举的集合放到左部。枚举 \(L'\),此时 \(x\) 被计入 \(R'\) 的条件是 \(S_x\subseteq L'\)。因此,可以设 \(g(S)=\sum_{x\in R}[S_x\subseteq S]B_x\),那么条件顺利地转化为了:
现在我们想要这个条件被破坏,不难算出最小吃掉的卷心菜数量 \(X\):
提醒一下,\(g\) 可以直接高维前缀和计算;同时,如果不存在 \(x\in R,S_x\subseteq S\),那么 \(g(S)\) 实际上为 \(-\infty\) 而非 \(0\)。因为根据最初的计算式,这样的 \(S\) 根本不会成为 \(\bigcup_{x\in R'}S_x\),所以我们不可以去考虑它。
考虑计算吃卷心菜的方案数。如果 \(X=0\),那么有且仅有“不吃”一种方案,我们将这种情况判掉。当 \(X>0\) 的时候,由于有最小化 \(X\) 的前提,所以我们必须保证存在一个最小化 \(f(L')-g(L')+1\) 的 \(L'\),使得我们吃的卷心菜的菜田集合为 \(L'\) 的子集;这样就可以保证 \(L'\) 的条件会被破坏。
形式化地,我们设 \(h(S)\) 为吃掉的卷心菜的菜田集合恰好为 \(S\) 的方案数,设 \(\mathcal{F}=\arg\min\{f(L')-g(L')\}\),那么答案就是:
而 \(h(S)\) 不难使用容斥计算:
这个也可以调用高维前缀和。我们最终得到了复杂度为 \(O(n2^n)\) 的算法。
小结:
- 注意 Hall 定理的形式,尤其是调换枚举的部的时候;由于右部将邻接点取并,所以左部 \(g(S)\) 才会计算 \(S_x\subseteq S\);
- 注意 \(g\) 的一些细节。如果有困惑,从初始的正确式子入手逐步理清楚。
代码
#include <cstdio>
#include <iostream>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int mod = 998244353, INF = 1e9;
const int MAXN = 25, MAXS = ( 1 << 20 ) + 5, MAXM = 2e6 + 5;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && 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;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
int fac[MAXM], ifac[MAXM];
int f[MAXS], g[MAXS], h[MAXS];
int cnt[MAXS], cont[MAXS];
int A[MAXN], B[MAXM], adj[MAXM];
int N, M;
inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }
inline 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;
}
inline void Init( const int n = 2e6 )
{
fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}
int main()
{
Init();
read( N ), read( M );
rep( i, 0, N - 1 ) read( A[i] ), f[1 << i] = A[i];
rep( i, 0, M - 1 ) read( B[i] );
rep( i, 0, N - 1 ) rep( j, 0, M - 1 )
{
int c; read( c );
if( c ) adj[j] |= 1 << i;
}
rep( i, 0, M - 1 ) g[adj[i]] += B[i];
for( int S = 1 ; S < ( 1 << N ) ; S ++ )
cnt[S] = cnt[S >> 1] + ( S & 1 );
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
f[S] = f[S & ( - S )] + f[S & ( S - 1 )];
for( int i = 0 ; i < N ; i ++ )
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( S >> i & 1 ) g[S] += g[S ^ ( 1 << i )];
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( ! g[S] ) g[S] = - INF;
int X = 1e9;
for( int S = 1 ; S < ( 1 << N ) ; S ++ )
X = std :: min( X, f[S] - g[S] + 1 );
X = std :: max( X, 0 );
if( X == 0 ) return puts( "0 1\n" ), 0;
for( int S = 1 ; S < ( 1 << N ) ; S ++ )
{
if( f[S] - g[S] + 1 == X ) cont[S] = 1;
int tmp = C( f[S], X );
if( ! tmp ) continue;
h[S] = cnt[S] & 1 ? mod - tmp : tmp;
}
for( int i = 0 ; i < N ; i ++ )
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
{
if( ! ( S >> i & 1 ) ) continue;
cont[S ^ ( 1 << i )] += cont[S];
h[S] = Add( h[S], h[S ^ ( 1 << i )] );
}
int ans = 0;
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( cont[S] ) ans = cnt[S] & 1 ? Sub( ans, h[S] ) : Add( ans, h[S] );
write( X ), putchar( ' ' ), write( ans ), putchar( '\n' );
return 0;
}
原文地址:https://www.cnblogs.com/crashed/p/15177582.html
- github pages + Hexo + 域名绑定搭建个人博客
- 使用ES6新特性开发微信小程序(3)——类
- linux学习第四篇:克隆虚拟机以及两台linux机器相互登录
- 前端开发的工具化与工程化
- 使用ES6新特性开发微信小程序(4)
- JavaScript闭包详解
- Spark你一定学得会(一)No.7
- iOS 滚动视图的复用问题解决方案
- 二叉排序树(BSTree)关于查找算法结合
- 使用ES6新特性开发微信小程序(5)——内建对象的变化
- 弗洛伊德(Floyd)算法
- 迪杰斯特拉(Dijkstra)算法求图中最短路径
- 微信小程序实例-摇一摇抽奖
- 普利姆(prim)算法和克鲁斯卡尔(kruskal)算法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- android使用viewpager计算偏移量实现选项卡功能
- Android画板开发之撤销反撤销功能
- Android实现复制Assets文件到SD卡
- Android画板开发之添加背景和保存画板内容为图片
- 直播插件体系设计
- linux 下selenium chrome使用详解
- WeTest:五年沉淀,打造游戏品质的坚实后盾
- ES5新增方法
- 筛选商品案例
- volatile关键字在Android中到底有什么用?
- Python无头爬虫下载文件的实现
- Python基于stuck实现scoket文件传输
- Python %r和%s区别代码实例解析
- Python基础类继承重写实现原理解析
- python torch.utils.data.DataLoader使用方法