[NOI2001]食物链(并查集拓展域)&& [HAOI2006]旅行(Kruskal)

2022-12-29,,,,

题目描述

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B

吃 C,C 吃 A。

现有 N 个动物,以 1 - N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道

它到底是哪一种。

有人用两种说法对这 N 个动物所构成的食物链关系进行描述:

第一种说法是“1 X Y”,表示 X 和 Y 是同类。

第二种说法是“2 X Y”,表示 X 吃 Y 。

此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真

的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

• 当前的话与前面的某些真的话冲突,就是假话

• 当前的话中 X 或 Y 比 N 大,就是假话

• 当前的话表示 X 吃 X,就是假话

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式

从 eat.in 中输入数据

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式

输出到 eat.out 中

一行,一个整数,表示假话的总数。


emmmmm, 最近学了并查集, 并查集能维护连通性和传递性, 这道题假如单单用一个并查集来维护谁与谁是同类, 显然无法判断互相吃的情况, 那么就容易想到用三个并查集去维护, 我们把每个动物x拆成3个节点, 分别表示同类域, 捕食域, 天敌域。假如x与y是同类, 那么x, y捕食的物种和天敌都是一样的, 把他们都合并。 而当x吃y时, 合并x的捕食域和y的同类域, x的同类域和y的天敌域, x的天敌域和y的捕食域。 在处理每句话之前, 首先要判断这句话是否为真。

当x与y同类:
1.x的捕食域与y的同类域在同一集合, 即x吃y。
2.x的同类域和y的捕食域在同一集合, 即y吃x、
当x吃y:
1.x的同类域和y的同类域在一个集合, 即x和y是同类。
2.x的同类域和y的捕食域在一个集合, 即y吃x。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 5e4 + ;
const int MAXM = 3e3 + ;
const double eps = 1e-; template < typename T > inline void read(T &x) {
x = ; T ff = , ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') ff = -;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << ) + (x << ) + (ch ^ );
ch = getchar();
}
x *= ff;
} template < typename T > inline void write(T x) {
if (x == ) {
putchar('');
return ;
}
if (x < ) putchar('-'), x = -x;
static T tot = , ch[];
while (x) {
ch[++tot] = x % + '';
x /= ;
}
while (tot) putchar(ch[tot--]);
} int n, m, ans, fa[MAXN * ]; inline int get(int x) {
return x == fa[x] ? x : fa[x] = get(fa[x]);
} int main() {
// freopen("1.in", "r", stdin);
read(n); read(m);
for (int i = ; i <= n * ; ++i) fa[i] = i;
while (m--) {
// printf("ans = %d\n", ans);
int op, x, y;
read(op); read(x); read(y);
if (x > n || y > n) {
++ans;
continue;
};
if (op == ) {
if (get(x + n) == get(y) || get(x) == get(y + n)) ++ans;
else {
fa[get(x)] = get(y);
fa[get(x + n)] = get(y + n);
fa[get(x + n + n)] = get(y + n + n);
} } else {
if (get(x) == get(y) || get(x) == get(y + n)) ++ans;
else {
fa[get(x + n)] = get(y);
fa[get(x + n + n)] = get(y + n);
fa[get(x)] = get(y + n + n);
}
}
}
write(ans);
return ;
}

题目描述

Z小镇是一个景色宜人的地方,吸引来自各地的观光客来此旅游观光。Z小镇附近共有N个景点(编号为1,2,3,…,N),这些景点被M条道路连接着,所有道路都是双向的,两个景点之间可能有多条道路。也许是为了保护该地的旅游资源,Z小镇有个奇怪的规定,就是对于一条给定的公路Ri,任何在该公路上行驶的车辆速度必须为Vi。速度变化太快使得游客们很不舒服,因此从一个景点前往另一个景点的时候,大家都希望选择行使过程中最大速度和最小速度的比尽可能小的路线,也就是所谓最舒适的路线。

输入格式

第一行包含两个正整数,N和M。

接下来的M行每行包含三个正整数:x,y和v。表示景点x到景点y之间有一条双向公路,车辆必须以速度v在该公路上行驶。

最后一行包含两个正整数s,t,表示想知道从景点s到景点t最大最小速度比最小的路径。s和t不可能相同。

输出格式

如果景点s到景点t没有路径,输出“IMPOSSIBLE”。否则输出一个数,表示最小的速度比。如果需要,输出一个既约分数。


emmmmmm, 刚看到这到题的时候, 我就想到了最小差值生成树, 这是我很早之前就做过的题了, 这个题算法上写着并查集, 我当时就想Kruskal和并查集有什么关系,  就专心致志的打我的dfs去了(我丢, 我好sb。。。),dfs的思路还是很容易想到的, 从s这个点开始枚举每一条路径, 把路径上所有边的最大最小值都记录下来, 当到t这个点是时候比较就可以了, 然后我就神奇的MLE, 在一些神仙讨论是数组开小还是爆内存的时候, 我默默地把dfs改成bfs, 结果依然MLE。 算了, 放弃这个暴力吧。 一位大佬提醒我一句, 把每条边加进去, 判断图是否连通。 此时我好像幡然醒悟, 这不就是一个裸的Kruskal嘛, 然后, 我也就真的打了一个Kruskal, 我丢, 我是没睡醒嘛, (其实确实有点瞌睡), 稍作修改就A掉了。 可以说这是一道写过的题了, 我甚至还hack正解, 在以后的学习中还是要学以致用啊。。。。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 5e5 + ;
const int MAXM = 3e3 + ;
const double eps = 1e-; template < typename T > inline void read(T &x) {
x = ; T ff = , ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') ff = -;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << ) + (x << ) + (ch ^ );
ch = getchar();
}
x *= ff;
} template < typename T > inline void write(T x) {
if (x == ) {
putchar('');
return ;
}
if (x < ) putchar('-'), x = -x;
static T tot = , ch[];
while (x) {
ch[++tot] = x % + '';
x /= ;
}
while (tot) putchar(ch[tot--]);
} //bool cur1; int n, m, s, t, q1, q2, fa[MAXN];
struct tree {
int x, y, v;
bool operator < (const tree &a) const {
return v < a.v;
}
}T[]; inline int gcd(int x, int y) {
return x == ? y : gcd(y % x, x);
} inline int get(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
} inline void Kruskal(int ss) {
for (int i = ; i <= n; ++i) {
fa[i] = i;
}
int maxx = -INF, minn = INF;
for (int i = ss; i <= m; ++i) {
int xx = get(T[i].x), yy = get(T[i].y);
if (xx != yy) {
fa[xx] = yy;
maxx = max(maxx, T[i].v);
minn = min(minn, T[i].v);
}
xx = get(s), yy = get(t);
if (xx == yy) break;
}
int xx = get(s), yy = get(t);
if (xx == yy) {
if ((double) maxx / minn < (double) q1 / q2)
q1 = maxx, q2 = minn;
}
} int main() {
read(n), read(m);
for (int i = ; i <= m; ++i) {
read(T[i].x);
read(T[i].y);
read(T[i].v);
}
read(s); read(t);
sort(T + , T + m + );
q1 = INF, q2 = ;
for (int i = ; i <= m; ++i) Kruskal(i);
if (q1 == INF) puts("IMPOSSIBLE");
else {
int g = gcd(q1, q2);
q1 /= g, q2 /= g;
write(q1);
if (q2 != ) putchar('/'), write(q2);
}
return ;
}

[NOI2001]食物链(并查集拓展域)&& [HAOI2006]旅行(Kruskal)的相关教程结束。

《[NOI2001]食物链(并查集拓展域)&& [HAOI2006]旅行(Kruskal).doc》

下载本文的Word格式文档,以方便收藏与打印。