P1955 [NOI2015] 程序自动分析题解

题目传送门

一、惯性思维

我是刚刚做完上一题P1892团伙 后来做这一道题,粗略一看,相等,不相等,这不就是朋友的朋友,敌人的敌人吗?小意思!种类并查集模板走起!!!

但一看题解,才发现了自己的浅薄:

不等号不具备传递性,而等号具有传递性。abbc不能推出ac

这和朋友的朋友是朋友,敌人的敌人是朋友完全是两回事!!行不通

比如举出的反例 :

1
3
1 2 0
2 3 0
1 3 0

在我的方法的连接下,本应是三个互不相等的数却出现了冲突

而正确的做法正是利用了不等号不具备传递性,我们将所有等号操作率先合并完了以后,再判断所有不等号是否成立,及两数是否在同一集合中,就可已完成此题

二、并查集版本

并查集的思路已经很明显了:首先把“相等”的条件搞好,也就是用并查集并起来。之后遍历所有“不等”的条件,只要有一对在同一个集合里,就不可能满足。

(1)如果说两个数字相等,那么就合并为同一家族。
(2)如果说两个数字不等,就检查以前它们是不是同一家族的,如果是,就是说明出现了矛盾,直接输入NO,退出即可。

但现实很残酷,一道绿题哪能是这么容易过的,第二个测试点RE:

#include <bits/stdc++.h>

using namespace std;

int t;//表示需要判定的问题个数
int n;//表示该问题中需要被满足的约束条件个数
int x; //第一个数
int y; //第二个数
int e; //是否相等,1相等,0不相等

const int N = 1e6 + 10;
int fa[N];

//先记录,再离散化
struct Node {
    int x, y, e;
} a[N];

//先处理相等,再处理不相等的,这个排序器是用来决定相等优先的
bool cmp(const Node &a, const Node &b) {
    return a.e > b.e;
}


//要深入理解这个递归并压缩的过程
int find(int x) {
    if (fa[x] != x) fa[x] = find(fa[x]);
    return fa[x];
}

//加入家族集合中
void join(int c1, int c2) {
    int f1 = find(c1), f2 = find(c2);
    if (f1 != f2)fa[f1] = f2;
}

int main() {
    cin >> t;
    while (t--) {
        cin >> n;

        //初始化,因为有多个问题,每个问题问之前,都需要清理下场地
        memset(a, 0, sizeof a);
        memset(fa, 0, sizeof fa);
        //录入
        for (int i = 1; i <= n; i++)
            cin >> a[i].x >> a[i].y >> a[i].e;
        //排序,先处理相等的,再处理不相等的.这样才能保证亲戚都认领完了,再找出矛盾。
        sort(a + 1, a + 1 + n, cmp);

        //初始化并查集
        for (int i = 1; i < N; i++) fa[i] = i;

        bool success = true;
        for (int i = 1; i <= n; i++) {
            if (a[i].e == 1) join(a[i].x, a[i].y);
            else {
                int f1 = find(a[i].x), f2 = find(a[i].y);
                if (f1 == f2) {
                    success = false;
                    break;
                }
            }
        }
        if (success) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

上面的代码也不错啊,可以获得大部分本题的得分。有一个核心的问题:就是排序。

为什么要排序呢?

因为并查集是一个家族关系的维护问题,我们的思路是创建家族关系,再找出不是一个家族关系的矛盾问题。

三、离散化+并查集版本

为什么会出现上面的情况呢?其实我们忽略了一个最基本的常知:OI中,必须关注数据范围~~

看到了没,这i,j都是上限1e9啊,我们开的数组fa[N],它能表示的范围是1e6+10,太小了,不死才怪!
那我们把N扩大到 1e9+10是不是就可以了呢??

当然不行了,要不还能叫绿题!!NOIP中,有如下的规则上限:
空间上限128MB

int=4字节
char=1字节
longlong=8字节
128M=1281024k=131072KB=134217728字节

结论:
int型一维数组最多是3千万
long long型一维数组最多是1千5百万
char型一维数组最多是1亿左右

C++完整代码

#include <bits/stdc++.h>

using namespace std;
//此题,数据范围太大,必须离散化处理(这是策略二,还需要一种简单版本的离散化)
//关注一下数据范围,是有10的9次方那么大,如果开一个10的9次方大的fa数组的话,空间肯定超限,超限就凉凉
int t;//表示需要判定的问题个数
int n;//表示该问题中需要被满足的约束条件个数
int x; //第一个数
int y; //第二个数
int e; //是否相等,1相等,0不相等

const int N = 1000010;
int fa[N];

//要深入理解这个递归并压缩的过程
int find(int x) {
    if (fa[x] != x) fa[x] = find(fa[x]);
    return fa[x];
}

//加入家族集合中
void join(int c1, int c2) {
    int f1 = find(c1), f2 = find(c2);
    if (f1 != f2)fa[f1] = f2;
}

//先记录,再离散化
struct Node {
    int x, y, e;
} a[N];

//先处理相等,再处理不相等的,这个排序器是用来决定相等优先的
bool cmp(const Node &a, const Node &b) {
    return a.e > b.e;
}

int main() {
    cin >> t;
    while (t--) {
        cin >> n;
        //初始化
        memset(a, 0, sizeof a);
        memset(fa, 0, sizeof fa);
        //录入+离散化
        vector<int> alls; // 存储所有待离散化的值
        for (int i = 1; i <= n; i++) {
            cin >> a[i].x >> a[i].y >> a[i].e;
            alls.push_back(a[i].x), alls.push_back(a[i].y);
        }
        // 将所有值排序
        sort(alls.begin(), alls.end());
        // 去掉重复元素
        alls.erase(unique(alls.begin(), alls.end()), alls.end());
        // 二分求出x对应的离散化的值
        for (int i = 1; i <= n; i++) {
            a[i].x = lower_bound(alls.begin(), alls.end(), a[i].x) - alls.begin();
            a[i].y = lower_bound(alls.begin(), alls.end(), a[i].y) - alls.begin();
        }

        //排序,先处理相等的,再处理不相等的.这样才能保证亲戚都认领完了,再找出矛盾。
        sort(a + 1, a + 1 + n, cmp);
        //初始化并查集
        for (int i = 1; i <= alls.size(); i++) fa[i] = i;

        bool success = true;
        for (int i = 1; i <= n; i++) {
            if (a[i].e == 1) join(a[i].x, a[i].y);
            else {
                int f1 = find(a[i].x), f2 = find(a[i].y);
                if (f1 == f2) {
                    success = false;
                    break;
                }
            }
        }
        if (success) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

四、数据很水,MOD也一样过

这道题的出题人太良心了,数据很水,没有故意制造冲突数据。通过下面简单取模的办法,就可以把此题AC掉。但这不是正确的方法,还是推荐上面闫老师的离散化大法~,那个才是王道,下面的解法出问题的概率很高。

#include <bits/stdc++.h>

using namespace std;
//此题,数据范围太大,必须离散化处理(这是策略一,简单版本的离散化)
//关注一下数据范围,是有10的9次方那么大,如果开一个10的9次方大的fa数组的话,空间肯定超限,超限就凉凉

int t;//表示需要判定的问题个数
int n;//表示该问题中需要被满足的约束条件个数
int x; //第一个数
int y; //第二个数
int e; //是否相等,1相等,0不相等

const int N = 1000005;
const int MOD = 1000003; //从这值是从P1955_Prepare.cpp中计算到的
int fa[N];

//要深入理解这个递归并压缩的过程
int find(int x) {
    if (fa[x] != x) fa[x] = find(fa[x]);
    return fa[x];
}

//加入家族集合中
void join(int c1, int c2) {
    int f1 = find(c1), f2 = find(c2);
    if (f1 != f2)fa[f1] = f2;
}

//先记录,再离散化
struct Node {
    int x, y, e;
} a[N];

//先处理相等,再处理不相等的,这个排序器是用来决定相等优先的
bool cmp(const Node &a, const Node &b) {
    return a.e > b.e;
}

int main() {
    cin >> t; //需要判断问题的个数
    while (t--) {//多个问题
        cin >> n;
        //初始化,因为有多个问题,每个问题问之前,都需要清理下场地
        memset(a, 0, sizeof a);
        memset(fa, 0, sizeof fa);
        //录入+离散化
        for (int i = 1; i <= n; i++) {
            int x, y;
            cin >> x >> y >> a[i].e;
            a[i].x = x % MOD, a[i].y = y % MOD;
        }
        //排序,先处理相等的,再处理不相等的.这样才能保证亲戚都认领完了,再找出矛盾。
        sort(a + 1, a + 1 + n, cmp);

        //初始化并查集
        for (int i = 1; i <= N; i++) fa[i] = i;

        bool success = true;
        for (int i = 1; i <= n; i++) {
            if (a[i].e == 1) join(a[i].x, a[i].y);
            else {
                int f1 = find(a[i].x), f2 = find(a[i].y);
                if (f1 == f2) {
                    success = false;
                    break;
                }
            }
        }
        if (success) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

找出一个合适的质数:

#include <bits/stdc++.h>

using namespace std;
const int N = 10000810;

int primes[N], cnt;
bool st[N];

void get_primes(int n) {
    for (int i = 2; i <= n; i++) {
        if (!st[i]) primes[cnt++] = i; //没有被标识过,就增加到质数数组中去

        // 线性筛法核心:N 只会被它最小的质因子筛掉
        // 从小到大枚举所有可能满足条件的质数
        for (int j = 0; primes[j] <= n / i; j++) {
            st[primes[j] * i] = true;       //从小向大,所以这里可以这么写primes[j],标识st[primes[j] * i]已经标识过了
            //最小质因子就可以了,其它质因子放弃掉,看上面的链接里有原因说明
            if (i % primes[j] == 0) break;
        }
    }
}

int main() {
    get_primes( 1e6 + 10);
    for (int i = 0; i < cnt; i++) cout << primes[i] << " ";
    return 0;
}
posted @   糖豆爸爸  阅读(131)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2018-08-09 Python中包(package)的调用方式
2018-08-09 NotePad++ 配置Python工作环境
2017-08-09 高速备份还原MYSQL数据库
2013-08-09 删除重复记录(Mysql,SqlServer,Sqlite)
Live2D
点击右上角即可分享
微信分享提示