1 条题解

  • 0
    @ 2026-10-26 11:31:19

    思路与算法

    每张账单分配唯一编号,分别放入最小堆和最大堆,并用alive[id]标记未支付。每天插入新账单后,从最小堆顶清除所有已支付编号,取出当日最小账单并标记为已支付;随后清除最大堆顶已支付编号,取出最大账单并标记。已支付项在另一个堆中暂留,只有来到堆顶时才丢弃,这就是惰性删除。

    相同面额必须当成不同账单。用64位键(value<<21)|id同时保存面额和编号,面额优先比较,编号仅打破同面额的顺序。最多15000*100=1500000个编号,小于2^21,编号不会侵占面额位。两个堆预留总账单数上界,避免节点树结构的额外内存。

    正确性说明

    某时刻alive为真的编号恰是所有未支付账单:插入时加入,支付时准确删除一个编号。堆中包含所有仍存活的账单,可能还有失效项;清除失效堆顶后,堆顶就是所有存活账单的真正极值。先删除最小账单,再在剩余账单中取最大,确保同一张账单不会支付两次。每天保证至少两张可支付,第二次取出一定存在。其余存活项自然保留到下一天,因此所有输出与过程一致。

    复杂度

    令B为全部收到的账单数,B<=1500000。每个编号进入两个堆各一次、从各堆最多弹出一次,总时间O(B log(B+1)+N),两个堆及alive数组空间O(Bmax),Bmax=100N。数组初始化O(Bmax)也计入总开销,可写总时间O(Bmax+B log(B+1)+N)。输出N行。

    两个预留的uint64堆各最多8Bmax字节,alive最多Bmax+1字节,合计约25.5MB,加上少量运行时仍在原64MiB内实际验证。面额小于10^9,移位编码小于2^51,不溢出64位。

    易错点

    必须删除编号而非删除所有相同金额;两个极值相等时也要支付两张不同账单。先删最小,再清理最大堆,避免重复选中同一编号。M允许0,旧账单跨天保留。编号要覆盖150万,不能只留20位。实际最大规模包含全重复、递增唯一值及随机高面额,独立答案由压缩坐标与Fenwick重数顺序统计计算。

    参考代码(C++11)

    #include <cstdint>
    #include <functional>
    #include <iostream>
    #include <queue>
    #include <vector>
    using namespace std;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n;
        cin >> n;
        using Key = uint64_t;
        vector<Key> lo, hi;
        lo.reserve(n * 100);
        hi.reserve(n * 100);
        priority_queue<Key, vector<Key>, greater<Key>> low(greater<Key>{}, move(lo));
        priority_queue<Key, vector<Key>> high(less<Key>{}, move(hi));
        vector<unsigned char> alive(n * 100 + 1);
        const Key mask = (Key(1) << 21) - 1;
        int id = 0;
        for (int day = 0; day < n; ++day) {
            int m;
            cin >> m;
            while (m--) {
                int value;
                cin >> value;
                ++id;
                alive[id] = 1;
                Key key = (Key(value) << 21) | id;
                low.push(key);
                high.push(key);
            }
            while (!alive[low.top() & mask])
                low.pop();
            Key a = low.top();
            low.pop();
            alive[a & mask] = 0;
            while (!alive[high.top() & mask])
                high.pop();
            Key b = high.top();
            high.pop();
            alive[b & mask] = 0;
            cout << (a >> 21) << ' ' << (b >> 21) << '\n';
        }
    }
    
    • 1

    信息

    ID
    1715
    时间
    1000ms
    内存
    64MiB
    难度
    普及
    标签
    递交数
    1
    已通过
    1
    上传者