LOJ6503. 「雅礼集训 2018 Day4」Magic(容斥原理+NTT)
题目链接
题解
题中要求本质不同的序列数量,不太好搞。我们考虑给相同颜色的牌加上编号,这样所有牌都不相同。那么如果我们求出了答案,只需要将答案除以 \(\prod a_i!\) 就好了。
“恰好有 \(k\) 对”不能直接求,考虑容斥,如果我们求出了 \(g(x)\) 表示至少有 \(x\) 对的方案数,那么答案即为 \(\sum_\limits{i = k}^{n} (-1)^{i - k}\binom{i}{k}g(i)\)。现在的问题是如何求 \(g(x)\)。
我们将这 \(k\) 个魔术对分配到 \(m\) 种颜色中去,即:第 \(i\) 种颜色含有 \(b_i\) 个魔术对(满足 \(b_i < a_i\))。接下来可以做一个 dp,依次考虑每一种颜色,设 \(f_{i, j}\) 表示考虑了 \(i\) 种颜色,且已有 \(j\) 个魔术对的方案数。对于 \(i\),我们需要计算第 \(i\) 种颜色有 \(b_i\) 个魔术对的方案数是多少。由于我们已经视所有牌均不同,因此我们可以从中任选 \(b_i\) 张牌来作为所有魔术对的第二张牌,方案数为 \(\binom{a_i}{b_i}\)。接下来依次将这 \(b_i\) 张牌插入到原序列中去。由于必须插到相同颜色的牌的后面,因此第一张牌有 \(a_i - b_i\) 个位置可以插入,第二张牌有 \(a_i - b_i + 1\) 个位置可以插入......因此总方案数为 \(\binom{a_i}{b_i} \times (a_i - b_i)(a_i - b_i + 1)\cdots(a_i - 1) = \binom{a_i}{b_i} \times \frac{(a_i - 1)!}{(a_i - b_i - 1)!}\)。这样,我们只需枚举 \(b_i\) 然后转移即可。
不过注意这样求得的 \(f_{m, x}\) 并不是最终的 \(g(x)\)。由于构成魔术对的 \(x\) 张牌已经确定,不构成魔术对的 \(n - x\) 张牌可以任意排列,因此 \(g(x) = f_{m, x} \times (n - x)!\)。
这样做 dp 的复杂度是 \(O(nm)\) 的,可以过 64 分。
注意到上面的 dp 其实是做了 \(m\) 次卷积,因此我们可以使用 NTT 进行优化。不过直接做 \(m\) 次多项式乘法时间复杂度并不能得到优化,注意到 \(f_{i, j}\) 中 \(j\) 一维的上界是不断合并增大的,因此我们可以使用一个堆,每次取出长度最小的两个数组进行 NTT 合并即可,这样复杂度就有保证了,时间复杂度为 \(O(m \log^2n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long ll;
typedef long double ld;
typedef unsigned int uint;
typedef pair<int, int> pii;
typedef unsigned long long ull;
template<typename T> inline void read(T& x) {
char c = getchar();
bool f = false;
for (x = 0; !isdigit(c); c = getchar()) {
if (c == '-') {
f = true;
}
}
for (; isdigit(c); c = getchar()) {
x = x * 10 + c - '0';
}
if (f) {
x = -x;
}
}
template<typename T, typename... U> inline void read(T& x, U&... y) {
read(x), read(y...);
}
template<typename T> inline bool checkMax(T& a, const T& b) {
return a < b ? a = b, true : false;
}
template<typename T> inline bool checkMin(T& a, const T& b) {
return a > b ? a = b, true : false;
}
const int N = 2e5 + 10, mod = 998244353, G = 3;
inline void add(int& x, int y) {
x = (x + y) % mod;
}
inline void mul(int& x, int y) {
x = 1ll * x * y % mod;
}
inline int qpow(int v, int p) {
int res = 1;
for (; p; p >>= 1, mul(v, v)) {
if (p & 1) {
mul(res, v);
}
}
return res;
}
int fac[N], invfac[N];
inline int binom(int n, int m) {
return 1ll * fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}
void init(int n) {
fac[0] = invfac[0] = 1;
for (register int i = 1; i <= n; ++i) {
mul(fac[i] = fac[i - 1], i);
}
invfac[n] = qpow(fac[n], mod - 2);
for (register int i = n - 1; i; --i) {
mul(invfac[i] = invfac[i + 1], i + 1);
}
}
int a[N], b[N], l, r[N], S;
inline void ntt_init(int v) {
for (l = 0, S = 1; S <= v; ++l, S <<= 1); --l;
for (register int i = 0; i < S; ++i) {
r[i] = (r[i >> 1] >> 1) | ((i & 1) << l);
}
memset(a, 0, (sizeof (int)) * S);
memset(b, 0, (sizeof (int)) * S);
}
inline void ntt(int* c, int type) {
for (register int i = 0; i < S; ++i) {
if (i < r[i]) {
swap(c[i], c[r[i]]);
}
}
for (register int i = 1; i < S; i <<= 1) {
int x = qpow(G, type == 1 ? (mod - 1) / (i << 1) : mod - 1 - (mod - 1) / (i << 1));
for (register int j = 0; j < S; j += i << 1) {
int y = 1;
for (register int k = 0; k < i; ++k, mul(y, x)) {
int p = c[j + k], q = 1ll * y * c[i + j + k] % mod;
c[j + k] = (p + q) % mod;
c[i + j + k] = (p - q + mod) % mod;
}
}
}
if (type == -1) {
int inv = qpow(S, mod - 2);
for (register int i = 0; i < S; ++i) {
mul(c[i], inv);
}
}
}
int m, n, k, v[N], g[N];
struct State {
int id, v;
State () {}
State (int id, int v): id(id), v(v) {}
bool operator < (const State& a) const {
return v > a.v;
}
};
priority_queue<State> Q;
vector<int> s[N];
int main() {
read(m, n, k);
init(n);
for (register int i = 1; i <= m; ++i) {
read(v[i]), Q.push(State(i, v[i] - 1));
for (register int j = 0; j < v[i]; ++j) {
s[i].pb((int) (1ll * binom(v[i], j) * fac[v[i] - 1] % mod * invfac[v[i] - j - 1] % mod));
}
}
for (register int i = 1; i < m; ++i) {
State x = Q.top(); Q.pop();
State y = Q.top(); Q.pop();
int k = x.v + y.v;
ntt_init(k);
for (register int j = 0; j < s[x.id].size(); ++j) {
a[j] = s[x.id][j];
}
for (register int j = 0; j < s[y.id].size(); ++j) {
b[j] = s[y.id][j];
}
s[x.id].clear(), s[y.id].clear();
ntt(a, 1), ntt(b, 1);
for (register int j = 0; j < S; ++j) {
mul(a[j], b[j]);
}
ntt(a, -1);
for (register int j = 0; j <= k; ++j) {
s[x.id].pb(a[j]);
}
Q.push(State(x.id, k));
}
int inv = 1;
for (register int i = 1; i <= m; ++i) {
mul(inv, fac[v[i]]);
}
inv = qpow(inv, mod - 2);
int p = Q.top().id;
for (register int i = 0; i < s[p].size(); ++i) {
g[i] = 1ll * s[p][i] * fac[n - i] % mod * inv % mod;
}
int ans = 0;
for (register int i = k; i <= n; ++i) {
if (i - k & 1) {
add(ans, mod - 1ll * binom(i, k) * g[i] % mod);
} else {
add(ans, 1ll * binom(i, k) * g[i] % mod);
}
}
printf("%d\n", ans);
return 0;
}
LOJ6503. 「雅礼集训 2018 Day4」Magic(容斥原理+NTT)的更多相关文章
- Loj #6503. 「雅礼集训 2018 Day4」Magic
Loj #6503. 「雅礼集训 2018 Day4」Magic 题目描述 前进!前进!不择手段地前进!--托马斯 · 维德 魔法纪元元年. 1453 年 5 月 3 日 16 时,高维碎片接触地球. ...
- LOJ#6503.「雅礼集训 2018 Day4」Magic[容斥+NTT+启发式合并]
题意 \(n\) 张卡牌 \(m\) 种颜色,询问有多少种本质不同的序列满足相邻颜色相同的位置数量等于 \(k\). 分析 首先本质不同不好直接处理,可以将同种颜色的卡牌看作是不相同的,求出答案后除以 ...
- 【loj#6503.】「雅礼集训 2018 Day4」Magic(生成函数+容斥)
题面 传送门 题解 复杂度比较迷啊-- 以下以\(n\)表示颜色总数,\(m\)表示总的卡牌数 严格\(k\)对比较难算,我们考虑容斥 首先有\(i\)对就代表整个序列被分成了\(m-i\)块互不相同 ...
- LOJ6502. 「雅礼集训 2018 Day4」Divide(构造+dp)
题目链接 https://loj.ac/problem/6502 题解 中间一档部分分提示我们将所有的 \(w_i\) 排序. 考虑如果我们能构造出这样一个 \(w_i\) 的序列,使得该序列满足:对 ...
- Loj#6503-「雅礼集训 2018 Day4」Magic【分治NTT】
正题 题目链接:https://loj.ac/p/6503 题目大意 \(n\)张卡\(m\)种,第\(i\)种卡有\(a_i\)张,求所有排列中有\(k\)对相邻且相同的卡牌. \(1\leq n\ ...
- 「雅礼集训 2018 Day2」农民
传送门 Description 「搞 OI 不如种田.」 小 D 在家种了一棵二叉树,第 ii 个结点的权值为 \(a_i\). 小 D 为自己种的树买了肥料,每天给树施肥. 可是几天后,小 D 却 ...
- 2018.10.27 loj#6035. 「雅礼集训 2017 Day4」洗衣服(贪心+堆)
传送门 显然的贪心题啊...考试没调出来10pts滚了妙的一啊 直接分别用堆贪心出洗完第iii件衣服需要的最少时间和晾完第iii件衣服需要的最少时间. 我们设第一个算出来的数组是aaa,第二个是bbb ...
- 【loj - 6516】「雅礼集训 2018 Day11」进攻!
目录 description solution accepted code details description 你将向敌方发起进攻!敌方的防御阵地可以用一个 \(N\times M\) 的 \(0 ...
- LOJ #6509. 「雅礼集训 2018 Day7」C
神仙题 LOJ #6509 题意 给定一棵树,点权为0/1,每次随机一个点(可能和之前所在点相同)走到该点并将其点权异或上1 求期望的移动距离使得所有点点权相同 题解 根本不会解方程 容易发现如果一个 ...
随机推荐
- XP下,移动窗口产生重影的问题
最近做了一个东西,其中有一个小窗口需要跟着主窗口一起移动,结果发现在Xp系统上总是产生重影,需要刷新桌面才能消失. 移动窗口我使用的是MoveWindow,最后一个参数bRepaint传递的是FALS ...
- 686. Repeated String Match 字符串重复后的子字符串查找
[抄题]: Given two strings A and B, find the minimum number of times A has to be repeated such that B i ...
- 680. Valid Palindrome II 对称字符串-可删字母版本
[抄题]: Given a non-empty string s, you may delete at most one character. Judge whether you can make i ...
- linux环境配置与使用合集
配置linux和samba共享 1. 安装linux操作系统 2. 通过windows操作系统ping linux看看是否可以ping通 3. 相关软件安装 a. 安装samba sudo apt-g ...
- Django框架 之 admin管理工具(组件使用)
Django框架 之 admin管理工具(组件使用) 浏览目录 激活管理工具 使用管理工具 admin的定制 admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理 ...
- layer弹出框插件参数及方法介绍
layerui下载:http://www.layui.com 更多参数请阅读开发文档:http://www.layui.com/doc/modules/layer.html Layui 是一款采用自身 ...
- 编写高质量代码改善C#程序的157个建议——建议31:在LINQ查询中避免不必要的迭代
建议31:在LINQ查询中避免不必要的迭代 无论是SQL查询还是LINQ查询,搜索到结果立刻返回总比搜索完所有的结果再将结果返回的效率要高. 示例代码: class MyList : IEnumera ...
- 正确理解WPF中的TemplatedParent (转贴)
http://blog.csdn.net/idebian/article/details/8761388 (注:Logical Tree中文称为逻辑树,Visual Tree中文称为可视化树或者视觉树 ...
- DataType--类型基础
数据类型数据的定义在各种场合均不一样,数据的载体也不一样,数据的表现方式和传递方式也不一样,数据的处理方式也不一样,数据库不可能处理所有形式的数据,因此必须规范数据,按照类型划分和处理. 连续和离散( ...
- 数据库连接工具HeidiSql介绍(支持MySQL,MariaDB,Microsoft SQL或PostgreSQL)
前言 Navicat作为比较老牌的数据库连接工具知名度比较广,功能也比较完善,但对入门的广大初学者来讲,怎么去找安装的资源包是一大难题,虽然经过一些“渠道”能找到可以正常使用的绿色安装包,但从长期来讲 ...