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\),则满足上述条件的非递减数组的个数为:

\[\left(\binom{n+x+p}{n} - \binom{n+x-1}{n}\right) \cdot \left(\binom{n+y+q}{n} - \binom{n+y-1}{n}\right) \]

这个公式可由隔板法导出。上式中第一项表示红色位的贡献,第二项表示绿色位的贡献。\(\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;
}