【NOI 2015】程序自动分析 并查集与离散化处理

题目描述

在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。

考虑一个约束满足问题的简化版本:假设x1,x2,x3,…代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。

输入

输入文件的第1行包含1个正整数t,表示需要判定的问题个数。注意这些问题之间是相互独立的。

对于每个问题,包含若干行:
第1行包含1个正整数n,表示该问题中需要被满足的约束条件个数。
接下来n行,每行包括3个整数i,j,e,描述1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj。

输出

输出文件包括t行。

输出文件的第k行输出一个字符串“YES”或者“NO”(不包含引号,字母全部大写),“YES”表示输入中的第k个问题判定为可以被满足,“NO”表示不可被满足。

测试输入

2
2
1 2 1
1 2 0
2
1 2 1
2 1 1

测试输出

NO
YES

提示

在第一个问题中,约束条件为:x1=x2,x1≠x2。这两个约束条件互相矛盾,因此不可被同时满足。

在第二个问题中,约束条件为:x1=x2,x2=x1。这两个约束条件是等价的,可以被同时满足。

1≤n≤1000000

1≤i,j≤1000000000

题目分析

对于本题,目的是判断程序能否满足所有的输入,因为只有相同和不同两种关系,那么我们可以先处理所有的相同的关系,将相同的数对用并查集维护成一个个不相交集合或者相交集合,而后再处理不同的数对,一旦不同的数对在同一个集合中则发生矛盾,输出NO即可,遍历完所有的不同数对发现确实都属于不同的集合则输出YES,本题需要注意的是输入数字较大,需要用离散化进行处理,直接开数组是不行的

注意点

  1. 关于并查集的建立,本题由于数据量很大,所以需要用到路径压缩,注释的部分是不采用路径压缩时的代码,路径压缩目的能使一个集合在与另一个集合合并前是以一棵深度只有1的树,这样减少了部分查询的消耗(数据量大的时候更为明显)
int find(int x){
    if(p[x] == x) return x;
    else{
        p[x] = find(p[x]);
    }
    return p[x];    
//  while(p[x] != x){
//      x = p[x];
//  }
//  return x;
}
  1. 关于C++ STL中的unique()函数,它的作用是对一个已经排序完成的数组,实现“合并”相邻的相同数字,为什么合并要带引号呢?事实上,合并后的数组长度未发生变化,它合并的过程是不断把后面的元素覆盖前面重复元素(具体就不作讲解了),在对数组执行unique操作时,一般接受两个参数,数组的首地址和尾地址,而它的返回值是完成查重后的数组的有序位最后一个+1的引用,就是说数组1,1,2,2,4,5,5,6通过unique(a, a+8)之后数组就变成了1,2,4,5,6,5,5,6只有前5个是有序的,且int t = unique(a, a+8)存放的是a[5]的引用,所以如果想得到具体是第几个那么我们一般写成int t = unique(a, a + 8) - a;那么t自然而然就是5,下面是代码
#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;

int main(){
    int a[] = {1, 1, 2, 2, 4, 5, 5, 6};
    int t = unique(a, a + 8) - a;
    cout<<t<<endl;
    cout<<a[t]<<endl;
    for(int i = 0; i <= 7; i++){
        cout<<a[i]<<endl;
    }
    return 0;
}
  1. 关于lower_bound()函数的使用,这是一个高效的方法用二分查找数组中第一次出现某个数的引用,它接收三个参数,数组首地址,数组尾地址,需要查询的数的值,一般我们将查询出的结果减去数组名,则得到一个数组中下标为i的位置是第一次出现查找的number的位置
 i = lower_bound(a + 1, a + n + 1, number) - a;
  1. 离散化:因为数据量i,j很大,而最多有1000000对输入,显然最多有2000000个不同的数字,我们可以构建一个2000000大的数组存放这所有的输入的i,j(尽管它们本身可能大大超出2000000但放在这个数组里却没有任何问题)

本题代码

#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;

const int N = 1000005;
int p[N<<1];
int m[N<<1];
int x[N];
int y[N];
int type[N];
int n, cnt, num;
struct Node{            //结构体是用于存放那些不同的数对的i j的第一次在数组中出现的位置 
    int x, y;
}k[N<<1];

void merge(){           //合并重复元素,实际上是覆盖操作,数组长度未发生变化,temp存放最后一个有序位置的索引(因为-m-1) 
    sort(m + 1, m + num + 1);
    int temp = unique(m + 1, m + num + 1) - m - 1;
    for(int i = 1; i <= n; i++){
        x[i] = lower_bound(m + 1, m + temp + 1, x[i]) - m;  //lowerbound()前两个参数的闭区间和开区间 
        y[i] = lower_bound(m + 1, m + temp + 1, y[i]) - m;  //执行完成后x[i]存放m数组中第一次出现x[i]的位置 
    }
}

int find(int x){
    if(p[x] == x) return x;
    else{
        p[x] = find(p[x]);
    }
    return p[x];    
//  while(p[x] != x){
//      x = p[x];
//  }
//  return x;
}

void Union(int x, int y){
    int fx = find(x);
    int fy = find(y);
    if(fx != fy) p[fy] = fx;
}

int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        cnt = 0;
        num = 0;
        for(int i = 1; i <= n; i++){                    //对输入的n组输入进行2*n的离散化处理 
            scanf("%d%d%d", &x[i], &y[i], &type[i]);
            m[++num] = x[i];
            m[++num] = y[i];
        }
        for(int i = 1; i <= num; i++) p[i] = i;         //p数组存放根 
        merge();
        for(int i = 1; i <= n; i++){
            if(type[i] == 1){
                Union(x[i], y[i]);              //type == 1时 将根合并同时路径压缩,这里要注意,x[i] y[i] 
            }else{                              //存放的是第一次出现位置,只有这样才能在2000000空间中查询1000000000大的数 
                k[++cnt].x = x[i];              //k结构体数组存放type == 0时数对x[i],y[i]第一次在数组m中出现的位置 
                k[cnt].y = y[i];                
            }
        }
        int flag = 0;
        for(int i = 1; i <= cnt; i++){
            int fx = find(k[i].x);
            int fy = find(k[i].y);
            if(fx == fy){                       //如果根相同 则说明相等 矛盾 
                flag = 1;
                printf("NO\n");
                break;
            }
        }
        if(flag == 0){
            printf("YES\n");
        }
    }
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容