C. Count Close Pairs

\(2N\) 就已经提示了只能双指针

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n;
    cin >> n;
    
    auto f = [&](int i, int j) {
        cout << "? " << i+1 << ' ' << j+1 << endl;
        string s;
        cin >> s;
        return s == "Yes";
    };
    
    int ans = 0;
    int l = 0;
    rep(r, n) {
        while (l < r and !f(l, r)) l++;
        ans += r-l;
    }
    
    cout << "! " << ans << endl;
    
    return 0;
}

D. Placing Rooks

\(i\) 个棋子能留下来,当且仅当之后第 \(R_i\) 行和第 \(C_i\) 列上没有棋子,可以倒过来做,正着做的话需要记录每行上的上一个棋子对应的列,以及每列上的上一个棋子对应的行

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> col(n, -1), row(n, -1);
    
    int ans = 0;
    auto rm = [&](int r, int c) {
        row[c] = col[r] = -1;
        ans--;
    };
    
    rep(i, m) {
        int r, c;
        cin >> r >> c;
        --r; --c;
        if (col[r] != -1) rm(r, col[r]);
        if (row[c] != -1) rm(row[c], c);
        col[r] = c;
        row[c] = r;
        ans++;
    }
    
    cout << ans << '\n';
    
    return 0;
}

E. Range Flip

把最多 \(K\) 次“翻转区间”转化为「位置上颜色(面朝前/后)的分段变化」。每次对一个区间翻转相当于在该区间的左右端点各产生一次“奇偶性切换”。所以最多 \(K\) 个区间会产生最多 \(2K\) 次切换,因此整个序列被划分成至多 \(2K+1\) 个连续段,段内每张牌的朝向相同,并且朝向在相邻段之间交替。

dp[i][j] 表示当前已经处理到第 \(i\) 张卡片,且已经用了 \(j\) 段的情况下可以取得的最大总和

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main() {
    int n, k;
    cin >> n >> k;
    
    int k2 = k*2+1;
    vector<ll> dp(k2);
    
    rep(i, n) {
        int a, b;
        cin >> a >> b;
        
        rep(j, k2) dp[j] += j%2==0 ? a : b;
        rep(j, k2-1) dp[j+1] = max(dp[j+1], dp[j]);
    }
    
    cout << dp[k2-1] << '\n';
    
    return 0;
}

F. Many Mod Calculation

统计区间 \([1,X]\) 中多少个 \(x\) 经过序列模运算后变为 \(0\),可以等价地统计 $[0,X] 中多少个数最终余数为 \(0\) 最后再去掉 \(0\) 本身即可。

用大根堆把所有待处理的数按“当前值”(即经过若干次取模后的结果)进行合并,不逐个枚举每个整数,而是用“值-计数”对来压缩表示。初始时每个数 \(x∈[0,X]\) 的当前值就是 \(x\),但可以把它们合并成若干块来快速处理:每个块记为 \((w,c)\),表示存在 \(c\) 个不同“块”,每个块包含恰好 \(w\) 个连续的当前值(也可以理解为有 \(c\) 个值集合,其值域大小为 \(w\))。总长度(所有块的 \(w⋅c\) 之和)始终为 \(X+1\)

对每个模数 \(a_i\) 的处理规则(将当前分布更新为取模后的分布):

  • 对于一个块 \((w,c)\),若 \(w≤a_i\),该块的所有当前值都小于 \(a_i\),取模不会改变它们,块保持不变;
  • \(w > a_i\) ,则可以把每个长度为 \(w\) 的块,按商和余数分解成 \(⌊\frac{w}{a_i}⌋\) 个“完整组”(每组对应一整段长度 \(a_i\) 的值域)和一个余组(长度 \(w \bmod a_i\))。总体上,所有被分出的完整组的数量会被合并为新的块,其块长为
    \(a_i\),计数为这些完整组的总数;所有余组(长度小于 \(a_i\))继续留在堆中作为较小的块。
  • 这样一次处理把整体的“当前值分布”重新压缩为一组不同大小的块,表示取模后的新分布。

重复对 \(a_1, a_2, \cdots,a_N\) 依次应用上述分裂/合并过程,最后得到的一组块正好描述了对所有模操作之后各个可能余数值的计数分布。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

void solve() {
    int n; ll x;
    cin >> n >> x;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
    
    priority_queue<pair<ll, ll>> q;
    q.emplace(x+1, 1);
    rep(i, n) {
        ll num = 0;
        while (q.top().first > a[i]) {
            auto [w, c] = q.top(); q.pop();
            num += w/a[i]*c;
            q.emplace(w%a[i], c);
        }
        q.emplace(a[i], num);
    }
    
    ll ans = -1;
    while (q.size()) {
        auto [w, c] = q.top(); q.pop();
        if (w == 0) continue;
        ans += c;
    }
    
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

G. Segment Sum Constraints

引入前缀和数组 \(P_0, P_1, \dots, P_N\),其中 \(P_0 = 0\)。题目给定的条件可以重写为两组约束:

  • 等式约束:\(P_{R_i} - P_{L_i-1} = S_i\) (共 \(M\) 个)
  • 隐式不等式约束:因为 \(A_i\) 必须为正整数,所以 \(P_i - P_{i-1} \ge 1 \implies P_i - P_{i-1} - 1 \ge 0\) (共 \(N\) 个)

我们将 \(P_0 \sim P_N\)\(N+1\) 个变量看作图的顶点。每个等式约束 \(P_y - P_x = S\) 相当于在 \(x\)\(y\) 之间连了一条权值为 \(S\) 的边。
通过找出图中的所有连通块,我们可以把变量进行“打包”:

  • 如果等式约束本身相互矛盾(例如同一个块内两点间有两条路径且计算出的差值不同),则直接无解。
  • 否则,设图中有 \(w\) 个连通块。每个块只需要指定一个“代表元”(根节点的值 \(R_c\)),块内其他节点的值都可以表示为 \(R_c + \text{offset}\)。由于 \(P_0 = 0\) 所在的块根节点值被固定,整个系统实际上只剩下最多 \(8\) 个未知的自由元。

如果某个基础元素 \(A_i\) 游离在所有给定的区间和之外,这意味着它的增长不会破坏任何等式。只要其余相互关联的变量能找到至少一组正整数解,我们就可以把这个游离的 \(A_i\) 放大到任意大。因此,若有未被覆盖的元素且系统有合法解,答案为 Infinity

现在,问题转化为了:求有多少组非负整数自由元 \((R_1, R_2, \dots)\),使得代入后全部 \(N\) 个不等式 \(P_i - P_{i-1} - 1 \ge 0\) 同时成立。

鉴于自由元数量极少,且数值上限在 \(10^9\) 级别,我们可以按二进制位从低到高(逐位确定)进行数位 \(\text{dp}\)

  • 当我们决定了所有自由元在第 \(k\) 位的取值(\(0\)\(1\))时,我们就可以计算出每个不等式在第 \(k\) 位的计算结果。
  • 加减法和比较运算在二进制下可以通过进位(Carry)来传递。我们只需要在 DP 的状态中维护这 \(N\) 个不等式当前的进位值。由于高位没有数字时进位会快速收敛(通常在 \([-2, 2]\) 之间),总的状态数非常少。
  • 最终判定:当迭代完足够多的位数(如 \(40\) 位)后,检查所有不等式的最终进位状态。如果所有不等式的进位均 \(\ge 0\),说明该组合能够让所有 \(A_i \ge 1\) 成立,计入答案。
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using mint = modint998244353;
using P = pair<int, int>;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<vector<P>> g(n+1);
    vector<bool> covered(n);
    rep(i, m) {
        int l, r, s;
        cin >> l >> r >> s;
        --l;
        g[l].emplace_back(r, s);
        g[r].emplace_back(l, -s);
        for (int j = l; j < r; ++j) covered[j] = true;
    }
    
    bool inf = false;
    rep(i, n) if (!covered[i]) inf = true;
    
    vector<int> id(n+1, -1); vector<ll> dif(n+1);
    int w = 0;
    {
        auto f = [&](auto& f, int v, ll nd=0) -> bool {
            if (id[v] != -1) return dif[v] == nd;
            id[v] = w; dif[v] = nd;
            for (auto [u, d] : g[v]) {
                if (!f(f, u, nd+d)) return false;
            }
            return true;
        };
        rep(i, n+1) if (id[i] == -1) {
            if (!f(f, i)) {
                cout << 0 << '\n';
                return 0;
            }
            w++;
        }
    }
    
    using vi = vector<int>;
    map<vi, mint> dp;
    dp[vi(n)] = 1;
    rep(k, 40) {
        map<vi, mint> old;
        swap(dp, old);
        for (auto [s, num] : old) {
            rep(t, 1<<w) if (t%2 == 0) {
                vi ns = s;
                rep(i, n) {
                    int d = s[i] + (t>>id[i+1]&1) - (t>>id[i]&1);
                    ll df = dif[i+1]-dif[i]-1;
                    if (df < 0) d -= -df>>k&1;
                    else d += df>>k&1;
                    ns[i] = (d+10)/2-5;
                }
                dp[ns] += num;
            }
        }
    }
    
    mint ans;
    for (auto [s, num] : dp) {
        bool ok = true;
        rep(i, n) if (s[i] < 0) ok = false;
        if (ok) {
            ans += num;
            if (inf) {
                puts("Infinity");
                return 0;
            }
        }
    }
    
    cout << ans.val() << '\n';
    
    return 0;
}