您的位置:首页 > 其它

[日常训练] 我们爱序列

2017-07-23 20:21 204 查看

【问题描述】

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),因此根节点改变时co
120e2
l也要重新进行赋值

【代码1】

#include <iostream>
#include <cstdio>

using namespace std;
const int N = 1e6 + 6;
int rt
, fa
, a
, col
;
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
, x
, y
, a
;

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
]), putchar('\n');
fclose(stdin); fclose(stdout);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息