[日常训练] 我们爱序列

【问题描述】

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的操作,最终将形成一条条形如 abcd 的链,对于这一条条链,实际上我们只关心链的终点,跟其它点是没有太大关系的
  • 但可能有人要问了,若某一时刻存在这样的链 bcd ,之后又添加一个操作 ab ,则此时 a 应变为b,而不是变为 d
  • 很显然,若能够在链走必然要满足修改操作的时间戳不断递增,那么这里就可以直接把ab看作单独的一条链,因为它的时间戳比链上之后的边都要大
  • 那么上述的这些又该如何实现呢?我们记 f[a] 表示数字 a 沿着链能走到的终点,把每次修改操作的a,b参数记作 xi,yi ,按照时间戳从大到小来遍历,每次令 f[xi]=f[yi] ,也就逆向模拟了链上元素走的过程,而刚刚的之后添加 ab 的情况,因为时间戳足够大,这里会优先赋值 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值