[日常训练] 我们爱序列
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 31 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; }
相关文章推荐
- 日常训练 维护序列(包含区间除)
- 1、checkbox日常jquery操作。 现在我们以下面的html为例进行checkbox的操作。 <input id="checkAll" type="checkbox" />全选
- QUST日常训练(1)分数修改
- 算法训练 摆动序列
- 日常训练 Idiot 的方程
- [日常训练] 距离之和
- 入门训练 序列求和
- 日常训练 20170605 费用流
- 在 Perl看来, 字符串只有两种形式. 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串, perl管它叫string. 也就是说: Perl只熟悉两种编
- 开发人员的不断流动、让我们更加坚定信念,一定要控制好整个系统的底层架构、核心设计、日常质量检查工作
- 我们日常所用得方法解析
- 如何使用HttpModule来实现我们日常的应用:
- 算法训练 摆动序列
- 入门训练 序列求和
- QUST日常训练(1)乘积最大
- 1123: 【C语言训练】列出最简真分数序列*
- 蓝桥杯 入门训练 序列求和 (c语言)
- Codeforces Round #424 (Div. 2)A-B-C 日常训练
- 现有一个n个整数的序列,你要做的就是交换两个数的位置直到整个序列按照升序排列,那么将这个整数序列排好序,需要交换多少次?例如,1,2,3,5,4,我们只需要交换一次,即将5和4交换即可。
- 【蓝桥杯练习系统】 入门训练 序列求和