题解:洛谷 AT_abc464_c Plumage Palette
【题目来源】
洛谷:AT_abc464_c [ABC464C] Plumage Palette - 洛谷
【题目描述】
Takahashi observed \(N\) birds, numbered \(1, 2, \dots, N\), over \(M\) days.
Each of the birds that Takahashi observed has one of \(N\) colors, numbered \(1, 2, \dots, N\), and these birds have an interesting characteristic: their color can change during the observation period.
Bird \(i\) had color \(A_i\) in observations on or before day \(D_i - 1\), and color \(B_i\) in observations on or after day \(D_i\).
Here, if \(D_i = 1\), then the bird's color was \(B_i\) from the observation on day \(1\), and if \(A_i = B_i\), then the bird's color did not change during the observation period.
For each \(j = 1, 2, \dots, M\), find the number of different colors of birds on day \(j\).
高橋观察了 \(N\) 只鸟,编号为 \(1, 2, \dots, N\),观察期为 \(M\) 天。
高橋观察的每只鸟都有 \(N\) 种颜色之一,编号为 \(1, 2, \dots, N\),这些鸟有一个有趣的特征:它们的颜色在观察期间可以发生变化。
对于鸟 \(i\),在第 \(D_i - 1\) 天及之前的观察中,它的颜色为 \(A_i\);在第 \(D_i\) 天及之后的观察中,它的颜色为 \(B_i\)。
这里,如果 \(D_i = 1\),则该鸟从第 \(1\) 天的观察开始颜色就是 \(B_i\);如果 \(A_i = B_i\),则该鸟在观察期间颜色没有发生变化。
对于每个 \(j = 1, 2, \dots, M\),求第 \(j\) 天鸟的颜色种类数。
【输入】
The input is given from Standard Input in the following format:
\(N\) \(M\)
\(A_1\) \(D_1\) \(B_1\)
\(A_2\) \(D_2\) \(B_2\)
\(\vdots\)
\(A_N\) \(D_N\) \(B_N\)
【输出】
Output \(M\) lines.
The \(j\)-th line should contain the number of different colors of birds on day \(j\).
【输入样例】
6 7
1 3 2
2 6 5
5 5 1
3 3 5
4 1 6
6 3 6
【输出样例】
5
5
3
3
4
4
4
【核心思想】
-
问题分析:给定 \(N\) 只鸟,每只鸟 \(i\) 在第 \(D_i\) 天从颜色 \(A_i\) 变为颜色 \(B_i\)(\(D_i = 1\) 表示初始即为 \(B_i\))。需要求每一天 \(j \in [1, M]\) 的颜色种类数。关键观察是:每只鸟的颜色变化发生在确定的某一天,因此可以将所有鸟按变色天数排序,然后按天顺序处理。
-
算法选择:
- 离线排序 + 双指针:将所有鸟按变色天数 \(D_i\) 升序排序,用指针 \(j\) 顺序遍历,配合当前天数 \(i\) 进行同步推进
- 哈希表(
map)动态维护颜色集合:\(O(\log N)\) 维护每种颜色当前的鸟数量,键的数量即为颜色种类数
-
关键步骤:
- 初始化:读入所有鸟的信息,将所有 \(A_i\) 加入
mp(初始时所有鸟均为变色前颜色) - 排序:将鸟按 \(D_i\) 升序排列
- 按天处理(双指针同步推进):
- 枚举天数 \(i\) 从 \(1\) 到 \(M\),指针 \(j\) 从 \(1\) 开始
- 当 \(j \leq N\) 且 \(a[j].D = i\) 时,处理第 \(j\) 只鸟的变色:
mp[A_j]--:原颜色计数减 \(1\),若归零则从mp中删除该键mp[B_j]++:新颜色计数加 \(1\)- \(j \leftarrow j + 1\)
- 输出
mp.size()作为第 \(i\) 天的颜色种类数
- 对于没有鸟变色的天数,
mp不变,直接输出当前大小
- 初始化:读入所有鸟的信息,将所有 \(A_i\) 加入
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N + M + N \log N) = O(N \log N + M)\),排序 \(O(N \log N)\),\(N\) 次变色操作每次
map更新 \(O(\log N)\),\(M\) 天输出 \(O(M)\) - 空间复杂度:\(O(N)\),存储鸟的信息和颜色频率表
- 时间复杂度:\(O(N \log N + M + N \log N) = O(N \log N + M)\),排序 \(O(N \log N)\),\(N\) 次变色操作每次
-
离线排序与双指针的核心思想:
- 事件驱动转化:将"每天查询颜色种类数"转化为"按时间顺序处理变色事件",利用排序将分散在时间轴上的 \(N\) 个变色操作组织为有序事件序列
- 双指针同步:指针 \(i\) 遍历天数,指针 \(j\) 遍历变色事件,由于两者均单调递增,总时间复杂度为线性级别,避免了对每天枚举所有鸟的 \(O(NM)\) 暴力
- 集合大小即答案:
map的键数量直接表示当前存在的不同颜色数,无需额外统计,利用数据结构特性简化答案获取 - 延迟删除策略:颜色计数归零时才从
map中删除,保证键的存在性与颜色的存在性严格对应 - 适用于带时间戳的离散事件统计问题,尤其是需要按时间顺序维护动态集合的场景
【算法标签】
普及- #双指针
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 300005; // 最大鸟的数量
int n, m; // n: 鸟的数量, m: 观察天数
struct Node
{
int A, D, B; // A: 变色前的颜色, D: 变色的天数, B: 变色后的颜色
} a[N]; // 存储每只鸟的信息
map<int, int> mp; // mp[color]: 当前颜色为color的鸟的数量
// 按变色天数D升序排序
bool cmp(Node x, Node y)
{
return x.D < y.D;
}
int main()
{
cin >> n >> m; // 读入鸟的数量和观察天数
// 读入每只鸟的信息,并统计初始颜色(第1天之前的颜色,即所有A_i)
for (int i = 1; i <= n; i++)
{
cin >> a[i].A >> a[i].D >> a[i].B;
mp[a[i].A]++; // 初始时所有鸟都是颜色A_i
}
// 按变色天数排序,方便按天处理
sort(a + 1, a + n + 1, cmp);
// j: 当前处理到第j只鸟(按D排序后)
// 遍历每一天i
for (int i = 1, j = 1; i <= m; i++)
{
// 处理所有在第i天变色的鸟
// 注意:a[j].D == i 表示第j只鸟在第i天从A变为B
// 即第i-1天及之前是A,第i天及之后是B
while (j <= n && a[j].D == i)
{
// 颜色A的鸟数量减1(这只鸟变色了)
mp[a[j].A]--;
if (mp[a[j].A] == 0)
mp.erase(a[j].A); // 该颜色没有鸟了,从map中删除
// 颜色B的鸟数量加1
mp[a[j].B]++;
j++; // 处理下一只鸟
}
// 输出第i天的颜色种类数(map的大小)
cout << mp.size() << endl;
}
return 0;
}
【运行结果】
6 7
1 3 2
2 6 5
5 5 1
3 3 5
4 1 6
6 3 6
5
5
3
3
4
4
4
浙公网安备 33010602011771号