题目描述

小 K 在海港按时间记录了 nn 艘到达的船:第 ii 艘船在时刻 tit_i(秒)到达,载 kik_i 名乘客,国籍依次为 xi,1,…,xi,kix_{i,1},\dots,x_{i,k_i}。对每个 ii,统计满足 ti−86400<tj≤tit_i - 86400 < t_j \le t_i(最近 24 小时)的所有船 jj(1≤j≤i1 \le j \le i)上的乘客共来自多少个不同国籍,输出 nn 行。

输入:第一行一个正整数 nn;接下来 nn 行,每行前两个整数 ti,kit_i, k_i,后跟 kik_i 个整数表示乘客国籍。输出:nn 行,第 ii 行为第 ii 艘船到达后的统计结果。

数据范围:1≤n≤1051 \le n \le 10^5,∑ki≤3×105\sum k_i \le 3\times 10^5,1≤xi,j≤1051 \le x_{i,j} \le 10^5,1≤ti≤1091 \le t_i \le 10^9(tit_i 递增)。

样例 1:输入 3、1 4 4 1 2 2、2 2 2 3、10 1 3,输出依次为 3、4、4。

思路

tit_i 递增,窗口两端只会单调右移,按船为单位维护队列:船 ii 先整船进窗(每个国籍计数 +1),再把队首满足 tj≤ti−86400t_j \le t_i - 86400 的旧船整船出窗(计数 -1,注意窗口左端是开区间,取等即出)。用数组 cnt[国籍] 维护窗口内人数,减到 0 时不同国籍数 distinct 减一,每轮直接输出 distinct;每个乘客只进、出各一次,总复杂度 O(n+∑ki)O(n + \sum k_i)。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 12:10
# update_at: 2026-10-02 12:14

import sys
from collections import deque
from collections.abc import Iterator

WINDOW = 86400  # 统计窗口:船到达时刻往前 24 小时 = 86400 秒


def read_ships(lines: Iterator[bytes]) -> Iterator[tuple[int, list[int]]]:
    """从标准输入的行流里依次产出 (到达时间, 国籍列表),逐行读入、不整份驻留内存。

    每行前两个数是到达时间 t 和乘客数 k,其后恰好 k 个国籍。
    """
    n = int(next(lines))
    for _ in range(n):
        t, k, *nats = map(int, next(lines).split())  # k 只用于占位,国籍个数由行本身给出
        yield t, nats


def window_answers(ships: Iterator[tuple[int, list[int]]]) -> Iterator[int]:
    """依次产出每艘船到达后,窗口 (t-86400, t] 内的不同国籍数。

    船按到达时间递增,退出窗口的船只会出现在队首,整船出窗即可;
    计数归零的国籍立即删键,len(counter) 才恒等于窗口内的国籍数。
    """
    window: deque[tuple[int, list[int]]] = deque()  # 窗口内的船 (到达时间, 国籍列表)
    counter: dict[int, int] = {}                    # 窗口内每个国籍的人数
    for t, nats in ships:
        window.append((t, nats))
        for x in nats:
            counter[x] = counter.get(x, 0) + 1

        expire_before = t - WINDOW  # 早于该时刻到达的船已滑出 24 小时窗口
        while window[0][0] <= expire_before:
            _, old = window.popleft()
            for x in old:
                if counter[x] == 1:
                    del counter[x]  # 该国籍在窗口内只剩这一个人,随船一起消失
                else:
                    counter[x] -= 1

        yield len(counter)


def solve() -> None:
    ships = read_ships(sys.stdin.buffer)
    write = sys.stdout.buffer.write
    for answer in window_answers(ships):
        write(f"{answer}\n".encode())  # 逐行输出,不攒整份答案字符串


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 15:52
 * update_at: 2026-10-06 15:52
 */
#include <cstdio>

typedef long long ll;

const int MAXN = 100005;   // 船的数量上限
const int MAXP = 300005;   // 乘客总数上限(sum k <= 3*10^5)
const int MAXC = 100005;   // 国籍的值域上限

ll tim[MAXN];    // tim[i]:第 i 艘船的到达时间(秒)
int pcnt[MAXN];  // pcnt[i]:第 i 艘船的乘客数
int pst[MAXN];   // pst[i]:第 i 艘船的乘客在 nat[] 中的起始下标
int nat[MAXP];   // nat[]:按船的顺序展开存放的所有乘客国籍
int cnt[MAXC];   // cnt[c]:当前 24 小时窗口内国籍为 c 的乘客人数

int n;            // 船的数量
int tot = 0;      // nat[] 已存放的乘客总数
int left_id = 1;  // 窗口最左端还没过期的船的编号
int distinct = 0; // 当前窗口内不同国籍的数量

// 把第 i 艘船整船放进窗口:船上每个乘客的国籍计数 +1
// 船 i 的乘客存放在 nat[pst[i]+1 .. pst[i]+pcnt[i]](nat 从 1 开始存放)
void add_ship(int i) {
    for (int p = pst[i] + 1; p <= pst[i] + pcnt[i]; p++) {
        int c = nat[p];
        if (cnt[c] == 0) distinct++; // 这个国籍第一次进入窗口
        cnt[c]++;
    }
}

// 把第 i 艘船整船移出窗口:船上每个乘客的国籍计数 -1
void remove_ship(int i) {
    for (int p = pst[i] + 1; p <= pst[i] + pcnt[i]; p++) {
        int c = nat[p];
        cnt[c]--;
        if (cnt[c] == 0) distinct--; // 这个国籍完全离开了窗口
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld %d", &tim[i], &pcnt[i]);
        pst[i] = tot; // 船 i 的乘客从 nat[tot+1] 开始存放
        for (int j = 1; j <= pcnt[i]; j++) {
            tot++; // 先确定本乘客的下标,再读入
            scanf("%d", &nat[tot]);
        }
    }
    for (int i = 1; i <= n; i++) {
        add_ship(i); // 右端:第 i 艘船进窗
        // 左端:t_i 递增保证过期船都在队首,整船弹出;
        // 窗口是开左闭右区间 (t_i-86400, t_i],取等即出
        while (left_id <= i && tim[left_id] <= tim[i] - 86400) {
            remove_ship(left_id);
            left_id++;
        }
        printf("%d\n", distinct);
    }
    return 0;
}