1. 程式人生 > 實用技巧 >洛谷P1955 [NOI2015]程式自動分析

洛谷P1955 [NOI2015]程式自動分析

洛谷P1955 [NOI2015]程式自動分析

題目連結

洛谷P1955 程式自動分析

題目概述

存在\(n\)個變數\(x_1,x_2,\dots,x_n\),給出這\(n\)個變數之間的\(n\)個約束條件\(x_i = x_j \ or \ x_i \neq x_j\)判斷這些是否可以同時滿足,如果可以輸出YES,否則輸出NO.一組測試包含多個測試,輸入的開始給出測試的個數,每個測試相互獨立.變數之間的約束條件輸入的格式為\(i\quad j\quad e\),如果\(e = 1\)說明\(x_i = x_j\),如果\(e=0\)說明\(x_i \neq x_j.\)

題目分析

可以先根據相等的條件把相等的變數劃分到一個集合中,然後逐一的判斷每個不相等的條件,取出這兩個變數\(x_i,x_j\)

如果\(x_i,x_j\)屬於同一個值相等的集合,那麼這個約束條件不滿足,可以直接輸出NO,當所有的不相等的約束條件都滿足時輸出YES.

並查集就是這樣一種支援將元素按所屬的集合進行分類,可以找到集合中任意一個元素所屬的集合,核心操作是union_set(x,y)find_set(x).以每個集合的樹高用於合併時的優化,和查詢時採用路徑壓縮在找到元素所屬集合的根節點後,把從根節點到這個元素結點路徑上的所有結點的前驅改為根節點,從而在一次\(O(log(n))\)的時間福再度查詢後,下一次可以以\(O(1)\)的時間複雜度獲得這個元素的集合的根節點.經過這樣的union_setfind_set

操作,最後得到的並查集的一個類似於這種形式:

graph TD r((r)) a((a)) b((b)) dots((...)) k((k)) r---a r---b r---dots r---k

注意:但是題目中對於\(i,j\)範圍的限定是\(1\leq i,j \leq 10^9\),如果直接開一個\(10^{10}\)的陣列,外加一些其它的空間開銷,是沒法分配這樣大的空間的,如果陣列開的小,會有一個測試點出現RE,一個可行的方案是對輸入的資料進行離散化處理,輸入的資料最多有\(2n\)個,而\(n\)的範圍是\(1\leq n \leq 10^5\)

,空間開銷比較小,假設一組輸入的約束條件是:

10000 1999999 1
199999 99999999 0
92713821 23713324 1
10000 23713324 0

很明顯這樣的資料如果直接用陣列下標對映,陣列的開銷非常大,但實際上輸入的資料非常少,把輸入的每一個變數儲存起來,得到這樣的一個序列:

10000 1999999 199999 99999999 92713821 23713324 10000 23713324

然後按升序進行排列去重,得到:

10000 199999 1999999 23713324 92713821 99999999

這樣就建立了原來資料和現在資料在序列中下標的對映:

\[buf[0]=10000,buf[1]=199999,\dots \]

如果需要查詢某個數\(x\)在上面的有序序列中,可以用STL標準庫裡面的lower_bound方法,返回值是區間\([first,last)\)第一個不小於\(x\)的數在這個區間的位置,如果沒有,那麼返回\(last\),因為上面的序列是單調遞增的,並且查詢的\(x\)一定位於其中,所以最後得到的一定是數\(x\)\(buf\)中的位置.

經過上面的序列化處理後,利用並查集的模板可以輕鬆方便的解決這個題目

程式碼實現

/*
 * @Author: Shuo Yang
 * @Date: 2020-08-01 08:08:32
 * @LastEditors: Shuo Yang
 * @LastEditTime: 2020-08-01 10:50:03
 * @FilePath: /Code/luogu/P1955.cpp
 */ 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+5;
int s[N];
int heights[N];
int buf[N];

//並查集模板

void init_set(){
    memset(heights, 0,sizeof(heights));
    for(int i = 0;i < N; ++i)
        s[i] = i;
}

int find_set(int x){
    int r = x;
    while( s[r] != r){
        r = s[r];
    }
    int i = x;
    while( i != r){
        int j = s[i];
        s[i] = r;
        i = j;
    }
    return r;
}


void union_set(int x, int y){
    x = find_set(x);
    y = find_set(y);
    if( heights[x] == heights[y]){
        heights[x]++;
        s[y] = x;
    }else{
        if( heights[x] < heights[y])
            s[x] = y;
        else
            s[y] = x;
    }
}

//這道題實際處理的部分

struct node{
    int a,b;
};

node equ[N];
node nequ[N];
int cnt = 0;

int get_index(int x){
    return lower_bound(buf, buf+cnt, x) - buf;
}

void solve(int n, int m){
    init_set();
    for(int i = 0; i < n; ++i){
        union_set(get_index(equ[i].a), get_index(equ[i].b));
    }
    for(int i = 0; i < m; ++i){
        if(find_set(get_index(nequ[i].a)) == find_set(get_index(nequ[i].b))){
            cout<<"NO"<<endl;
            return;
        }
    }
    cout<<"YES"<<endl;
}

int main(int argc, const char** argv) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin>>t;
    while(t--){
        int k;
        cin>>k;
        int n = 0;
        int m = 0;
        cnt = 0;
        for(int i = 0; i < k; ++i){
            int a,b,f;
            cin>>a>>b>>f;
            if(f==1){
                equ[n++] = {a,b};
            }else{
                nequ[m++] = {a,b};
            }
            buf[cnt++] = a;
            buf[cnt++] = b;
        }
        //排序去重
        sort(buf, buf+cnt);
        cnt = unique(buf, buf+cnt)-buf;
        solve(n,m);
    }
    return 0;
}

其它