T1. 数据结构
注意到可以离线,考虑整体二分。每次执行前一半操作,如果发现超过了 \(y\),那么答案就在前一半操作,否则就在后一半操作(如果补一个操作编号为 \(0\),整体加极大值的操作)。
所以只需要维护操作区间 \([l, r]\) 和答案在 \([l, r]\) 内的询问集合即可。使用树状数组维护可以获得很高的效率。
T2. 点兵
首先可以建立费用流模型来跑,或者写个二分图最大权匹配,可以拿下 \(70\) 分。
有可能发现这是一道诈骗题,选择一个点 \((x, y)\) 意味着可以点亮第 \(i\) 行或第 \(j\) 列,最后要求每行每列都被点亮。
这启发你给 \((i, j+n)\) 连一条费用为 \(a_{i,j}\) 的边,现在问题变为有 \(n+m\) 个点,\(nm\) 条带权边,求最小生成基环森林。
这玩意直接用 \(\text{Kruskal}\) 做就好了,正确性可以用它做最小生成树的方法推。\(O(nm\log (nm))\)
T3. 数学作业
首先为了方便,我们将 \(m\) 加 \(1\),然后将对 \(a_i\) 的限制改为 \(0 \leqslant a_i < m\) 。
小观察:
我们只考虑前 \(21\) 位。
令 \(p\) 表示 \(k\) 中为 \(1\) 的位的集合,\(q\) 表示 \(k\) 中为 \(0\) 的位的集合。
那么数组在由 \(p\) 和 \(q\) 指定的位上都应当是非递减的。
解决本题的一般方法:
由于我们希望 \(a_i < m\),因此我们会选取 \(m\) 的某个前缀,并仅在最后一位使该前缀不匹配,这样后面的位就可以独立处理。
比如,如果 \(m\) 的二进制表示为 \(110110\),那么我们会考虑前缀 \(1, 11, 1101\),\(11011\),把这些前缀的最后一位改掉后得到 \(0, 10, 1100, 11010\) 。
注意我们保留了前缀 \(110\) 和 \(110110\),因为无法通过改变最后一位使它们变小。
现在我们在固定某个前缀 \(s\) 之后,我们需要计算满足下列条件的好数组 \(a\) 的个数:
- 每个 \(a_i\) 的前缀 \(\leqslant s\);
- \(a_n\) 的前缀 \(= s\);
- \(a\) 在之前定义的集合 \(p\) 和 \(q\) 对应的位上是非递减的。
在为每个前缀求出这些计数后,我们可以直接将它们相加以得到答案。由于每次都固定了 \(a_n\) 的前缀,因此不会有重复计数。
现在令 \(m=\color{red}{1}\color{green}{0}\color{red}{111}\color{green}{00}\color{red}{111}\)(二进制)。这里红色位表示属于集合 \(p\) 的位,绿色表示属于集合 \(q\) 的位。
我们考虑长度为 \(5\) 的前缀 \(10111\) 。把该前缀的最后一位改掉得到 \(10110\) 。现在对前缀中两种颜色的位进行压缩。于是前缀里的红色位要求每个 \(a_i\) 的红色位值 \(\leqslant \color{red}{11}\)(即十进制 \(3\));前缀里的绿色位要求每个 \(a_i\) 的绿色位值 \(\leqslant \color{green}{010}\)(即十进制 \(2\))。由于后缀与 \(m\) 没有不等式限制,所以只需要求 \(a\) 后缀中的红色位值 \(\leqslant 2^3-1\)(后缀有 \(3\) 个红色位),绿色位值 \(\leqslant 2^2-1\)(后缀有 \(2\) 个绿色位)。
设 \(a_n\) 的前缀中红色位的数值为 \(x\),绿色位的数值为 \(y\)(在所举的例子中 \(x=3\),\(y=2\))。
设 \(a_n\) 的后缀中红色位的数值为 \(p\),绿色位的数值为 \(q\)(在所举的例子中 \(0 \leqslant p \leqslant 2^2-1\),\(0 \leqslant q \leqslant 2^3-1\))。
我们先把 \(x\) 更新为 \(2^3 \cdot x\)(因为后缀中有 \(3\) 个红色位),把 \(y\) 更新为 \(2^2 \cdot y\)(因为后缀中有 \(2\) 个绿色位)。
现在红色位的总值为 \(x+p\),绿色位的总值为 \(y+q\) 。
我们希望 \(a\) 的红色值和绿色值都是非递减的。
把 \(a_n\) 的红色值固定为 \(x+p\)、绿色值固定为 \(y+p\),则满足上述条件的非递减数组的个数为:
这个公式可由隔板法导出。上式中第一项表示红色位的贡献,第二项表示绿色位的贡献。\(\binom{n+x+p}{n}\) 表示长度为 \(n\) 的非递减数组的数量,且每个元素都满足就 \(\geqslant 0\) 且 \(\leqslant x+p\)。因此减去 \(\binom{n+x-1}{n}\) 就得到末项为 \(x+p\) 的非递减数组的个数。
因此对每个前缀把上述公式累加即可得到答案。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
const int mod = 998244353;
//const int mod = 1000000007;
struct mint {
ll x;
mint(ll x=0):x((x%mod+mod)%mod) {}
mint operator-() const {
return mint(-x);
}
mint& operator+=(const mint a) {
if ((x += a.x) >= mod) x -= mod;
return *this;
}
mint& operator-=(const mint a) {
if ((x += mod-a.x) >= mod) x -= mod;
return *this;
}
mint& operator*=(const mint a) {
(x *= a.x) %= mod;
return *this;
}
mint operator+(const mint a) const {
return mint(*this) += a;
}
mint operator-(const mint a) const {
return mint(*this) -= a;
}
mint operator*(const mint a) const {
return mint(*this) *= a;
}
mint pow(ll t) const {
if (!t) return 1;
mint a = pow(t>>1);
a *= a;
if (t&1) a *= *this;
return a;
}
// for prime mod
mint inv() const {
return pow(mod-2);
}
mint& operator/=(const mint a) {
return *this *= a.inv();
}
mint operator/(const mint a) const {
return mint(*this) /= a;
}
};
istream& operator>>(istream& is, mint& a) {
return is >> a.x;
}
ostream& operator<<(ostream& os, const mint& a) {
return os << a.x;
}
struct modinv {
int n; vector<mint> d;
modinv(): n(2), d({0,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(-d[mod%n]*(mod/n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
int n; vector<mint> d;
modfact(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*n), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
int n; vector<mint> d;
modfactinv(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*invs(n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
if (n < k || k < 0) return 0;
return facts(n)*ifacts(k)*ifacts(n-k);
}
void solve() {
int n, m, k;
cin >> n >> m >> k;
++m;
int high = 0;
for (int i = 19; i >= 0; --i) {
if (m>>i&1) {
high = i;
break;
}
}
mint ans;
for (int i = high; i >= 0; --i) {
if (~m>>i&1) continue;
int x = 0, y = 0;
for (int j = high; j >= i; --j) {
if (j == i) {
if (~k>>j&1) x *= 2;
else y *= 2;
continue;
}
if (~k>>j&1) {
x *= 2;
if (m>>j&1) x++;
}
else {
y *= 2;
if (m>>j&1) y++;
}
}
int p = 0, q = 0;
for (int j = i-1; j >= 0; --j) {
if (~k>>j&1) {
p *= 2;
x *= 2;
p++;
}
else {
q *= 2;
y *= 2;
q++;
}
}
ans += (comb(n+x+p, n) - comb(n+x-1, n)) * (comb(n+y+q, n) - comb(n+y-1, n));
}
cout << ans << '\n';
}
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int t;
cin >> t;
while (t--) solve();
return 0;
}
浙公网安备 33010602011771号