【问题描述】
AngryBacon 非常喜欢序列,与序列有关的一切都喜欢。
AngryBacon 面前摆着一个长度为 N 的序列,每个元素为不超过 M 的正整数。
AngryBacon 会使用 Q 次魔法,每次魔法的内容为一对不超过 M 的正整数 a; b,表示将序列中所有 为 a 的数改写为 b。
AngryBacon 想知道在最后他心爱的序列变成了什么样。
【输入格式】
第一行,包含三个整数 N; M; Q,意义如上所述。
第二行,包含n个整数表示初始序列。接下来Q行,每行2个整数a,b。
【输出格式】
输出一行,包含 N 个整数,表示最后序列的形态。
【输入样例】
5 5 3
1 2 3 4 5
3 1
4 3
1 5
【输出样例】
5 2 5 3 5
【数据范围】
对于20%的数据:1 <= n, m, Q <= 1000。
对于100的数据:1 <= n,m,Q <= 1000000,1 <= a, b, Ai <= M。
【解法1】 并查集
- 以序列上的每个位置为点,每个出现的数字对应一棵树(类似于树?),一开始用并查集将所有点合并到其对应的树上,并记录每棵树的根节点 rt 以及代表的数字
- 若要将序列中的数字
A
全部改成
B ,我们记数字 A 对应的树为X ,数字 B 对应的数为Y ,则需要将 X 合并到Y 上,也就是在并查集中将 rtX 合并到 rtY 上,合并完后树 X 上就没有节点了,为了处理方便我们可以直接令rtX=0 - 同时还要考虑
X
和
Y 一开始就没有节点的情况,若树 X 没有节点,则我们可以直接不处理,若树Y 没有节点,则我们可以直接令 rtY=rtX - PS:下面的代码中我是直接在每棵树的根节点上记录其所代表的数字 (col) ,因此根节点改变时 col 也要重新进行赋值
【代码1】
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e6 + 6;
int rt[N], fa[N], a[N], col[N];
int n, m, Q, x, y;
inline int get()
{
char ch; int res = 0;
while ((ch = getchar()) < '0' || ch > '9');
res = ch - '0';
while ((ch = getchar()) >= '0' && ch <= '9')
res = (res << 3) + (res << 1) + ch - '0';
return res;
}
inline void put(int x)
{
if (x > 9) put(x / 10);
putchar(x % 10 + 48);
}
inline int Find(const int &x)
{
if (fa[x] != x) fa[x] = Find(fa[x]);
return fa[x];
}
inline void Merge(const int &x, const int &y)
{
int tx = Find(x), ty = Find(y);
if (tx != ty) fa[ty] = tx, col[ty] = 0;
}
int main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = get(); m = get(); Q = get();
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = 1; i <= n; ++i)
{
col[i] = a[i] = get();
if (rt[a[i]]) Merge(rt[a[i]], i);
else rt[a[i]] = i;
}
while (Q--)
{
x = get(); y = get();
if (!rt[x] || x == y) continue;
if (!rt[y]) col[rt[y] = rt[x]] = y;
else Merge(rt[y], rt[x]);
rt[x] = 0;
}
for (int i = 1; i < n; ++i)
put(col[Find(i)]), putchar(' ');
put(col[Find(n)]), putchar('\n');
fclose(stdin); fclose(stdout);
return 0;
}
【解法2】图论 + 乱搞(代码这么短??)
- 我们观察后发现:所有关于将数字
a
修改为
b 的操作,最终将形成一条条形如 a→b→c→d…… 的链,对于这一条条链,实际上我们只关心链的终点,跟其它点是没有太大关系的 - 但可能有人要问了,若某一时刻存在这样的链
b→c→d……
,之后又添加一个操作
a→b
,则此时
a
应变为
b ,而不是变为 d 。 - 很显然,若能够在链走必然要满足修改操作的时间戳不断递增,那么这里就可以直接把
a→b 看作单独的一条链,因为它的时间戳比链上之后的边都要大 - 那么上述的这些又该如何实现呢?我们记
f[a]
表示数字
a
沿着链能走到的终点,把每次修改操作的
a,b 参数记作 xi,yi ,按照时间戳从大到小来遍历,每次令 f[xi]=f[yi] ,也就逆向模拟了链上元素走的过程,而刚刚的之后添加 a→b 的情况,因为时间戳足够大,这里会优先赋值 f[a]=f[b] ,正确性就有了保证。
【代码2】
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e6 + 5;
int n, m, Q;
int f[N], x[N], y[N], a[N];
inline int get()
{
char ch; int res = 0;
while ((ch = getchar()) < '0' || ch > '9');
res = ch - '0';
while ((ch = getchar()) >= '0' && ch <= '9')
res = (res << 3) + (res << 1) + ch - '0';
return res;
}
inline void put(int x)
{
if (x > 9) put(x / 10);
putchar(x % 10 + 48);
}
int main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = get(); m = get(); Q = get();
for (int i = 1; i <= m; ++i) f[i] = i;
for (int i = 1; i <= n; ++i) a[i] = get();
for (int i = 1; i <= Q; ++i) x[i] = get(), y[i] = get();
for (int i = Q; i >= 1; --i) f[x[i]] = f[y[i]];
for (int i = 1; i < n; ++i) put(f[a[i]]), putchar(' ');
put(f[a[n]]), putchar('\n');
fclose(stdin); fclose(stdout);
return 0;
}