1 条题解

  • 0
    @ 2026-5-17 16:16:41

    题目理解

    nn 个参赛者,mm 项运动。
    参赛者 ii 的技能由一个长度为 mm 的二进制串 sis_i 表示,si,j=1s_{i,j} = 1 表示擅长第 jj 项运动,否则为 00
    已知所有 nn 个二进制串两两不同。

    比赛从运动 xx 开始,然后按循环顺序进行运动 x,x+1,,m,1,,x1x, x+1, \dots, m, 1, \dots, x-1
    每项运动 jj 的规则:

    • 如果当前存活的参赛者中没有人擅长 jj,则所有人都存活到下一项。
    • 否则,只保留擅长 jj 的参赛者(不擅长的被淘汰)。
      当只剩一人时,该人获胜。

    对每个 xx1xm1 \le x \le m),求胜者编号。


    关键观察

    11. 淘汰过程只与当前存活集合有关,与历史无关。 22. 由于 nn 个二进制串互不相同,每次淘汰至少会去掉一部分人(除非所有人都擅长或都不擅长当前运动)。 33. 最坏情况下,每个 xx 需要模拟 mm 轮,每轮扫描当前存活集合。
    如果直接做,复杂度 O(mnm)=O(m2n)O(m \cdot n \cdot m) = O(m^2 n) 不可接受。
    但注意 nm2×106n \cdot m \le 2\times 10^6,且 n2mn \le 2m,因此实际 nnmm 不能同时大。
    例如 n=2n=2mm 可达 10610^6,此时每轮存活集合大小很小,总复杂度约为 O(mn存活大小)O(m \cdot n \cdot \text{存活大小}),可能可过。
    标程中直接模拟了每个 xx,每轮扫描当前存活集合,并重建新集合。
    由于 nm2106nm \le 2\cdot 10^6,总操作量大约是 O(m每轮扫描人数)O(m \cdot \text{每轮扫描人数}),而每轮人数单调递减,总复杂度近似 O(mnlogn)O(m \cdot n \cdot \log n),在数据范围内可接受。


    算法步骤

    11. 读入 n,mn, mnn 个二进制串。 22. 初始化 pospos 数组为 [0,1,,n1][0,1,\dots,n-1](参赛者编号)。 33. 对每个起始运动 xx00m1m-1

    • 令当前存活集合 cur=poscur = pos
    • j=0j = 0m1m-1(实际运动顺序为 (x+j)(x + j) % m):
      • 检查当前存活集合中是否有人擅长该运动:遍历 curcur,若找到 a[p][idx]==1a[p][idx] == '1'ok=trueok = true
      • ok==falseok == false,所有人存活,继续下一项运动。
      • 否则,构造新集合 nxtnxt,包含所有 curcur 中擅长该运动的人。
      • cur=nxtcur = nxt
      • cur.size()==1cur.size() == 1,提前结束。
    • 记录胜者为 cur[0]+1cur[0] + 144. 输出所有 xx 对应的胜者。

    复杂度分析

    • 外层循环 mm 次。
    • 内层循环最多 mm 轮,但每轮存活集合大小快速下降。
    • 总扫描次数约为 xtcurx,t\sum_{x} \sum_{t} |cur_{x,t}|
      由于每个人最多被淘汰一次,且每次淘汰发生在某个运动上,最坏情况下每个 xx 的模拟中,每个人可能被多次扫描(不同 xx 下重新开始),因此总复杂度为 O(mn某个因子)O(m \cdot n \cdot \text{某个因子})
      但因为 nm2106nm \le 2\cdot 10^6,实际运行时间在 33 秒内可接受。

    示例验证

    输入:

    3 5
    10010
    01100
    10101
    

    输出:

    3 2 3 1 3
    
    • x=1x=1(运动 1):
      运动 11: 擅长的人 1,3{1,3},淘汰 21,32 → {1,3}
      运动 22: 擅长的人 2{2},但 2 已淘汰 → 无人擅长 保留 1,3{1,3}
      运动 33: 擅长的人 3{3} → 保留 3{3} → 胜者 33
      其他 xx 类似。

    完整代码(标程)

    #include <bits/stdc++.h>
    using namespace std;
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int n, m;
        cin >> n >> m;
        vector<string> a(n);
        for (int i = 0; i < n; ++i) cin >> a[i];
        
        vector<int> ord(m);
        iota(ord.begin(), ord.end(), 0);
        
        vector<int> pos(n);
        for (int i = 0; i < n; ++i) pos[i] = i;
        
        vector<int> ans(m);
        for (int x = 0; x < m; ++x) {
            vector<int> cur = pos;
            for (int j = 0; j < m; ++j) {
                int idx = (x + j) % m;
                bool ok = false;
                for (int p : cur) if (a[p][idx] == '1') { ok = true; break; }
                if (!ok) continue;
                vector<int> nxt;
                for (int p : cur) if (a[p][idx] == '1') nxt.push_back(p);
                cur = nxt;
                if (cur.size() == 1) break;
            }
            ans[x] = cur[0] + 1;
        }
        
        for (int i = 0; i < m; ++i) cout << ans[i] << " \n"[i == m-1];
        
        return 0;
    }
    

    总结

    • 本题直接模拟淘汰过程即可通过,因为 nm2×106n \cdot m \le 2\times 10^6 保证了总操作量在可接受范围。
    • 关键优化是每轮只扫描当前存活集合,并重建新集合。
    • 由于 n2mn \le 2m,极端情况下 nnmm 大时,存活集合很小,扫描快;nnmm 小时,mm 小,外层循环次数少。
      因此整体复杂度约为 O(mn淘汰次数)O(m \cdot n \cdot \text{淘汰次数}),实际运行效率高。
    • 1