1. 程式人生 > >UVA11987 【Almost Union-Find】

UVA11987 【Almost Union-Find】

這是一道神奇的題目,我調了大概一天多吧

首先hack一下翻譯,操作3並沒有要求查詢後從其所在集合裡刪除該元素

於是我們來看一下這三個操作

第一個合併屬於並查集的常規操作

第三個操作加權並查集也是可以解決的

至於第二個操作就是這個題的難點了

對於操作二的要求“ 將 \(p\) 移動至包含 \(q\) 的集合中,如果 \(p\)\(q\) 已經在一個集合中,忽視此項操作”,我們可以發現這個操作其實隱含了一個刪除的操作,即先從原集合中將 \(p\)刪除,再將其移動到集合\(q\)

我們知道並查集是並不支援刪除操作的,於是我們必須對於這個毀天滅地的操作二想出一些奇技淫巧

於是呢,對於操作二,我們可以用一個虛點來代替那個要被從某個集合中刪除的點,我們使這個虛點繼承這個要被刪除的點的所有狀態,比如說元素和、元素個數和他的代表元素是誰,而我們在以後\(find\)

里路徑壓縮時只要遇到以這個被刪除的點為代表元素的元素,我們把他們的代表元素更新成這個虛點就好了

至於我們如何在路徑壓縮裡判斷這個點是否已經被從被某個虛點代替,這裡就是真·奇技淫巧了

或許我們可以用map,但map的一次\(find\)複雜度可是\(O(log_2\ n)\)的,我們,可能只有我在這種多組資料的會t的非常欲仙欲死,於是我們的奇技淫巧就要登場了

它就是unordered_map

這是個什麼東西呢,我們都知道map是用紅黑樹實現的,所以map內部\(key\)是有序的,但這也導致我們查詢一個元素是否存在是要\(O(log_2\ n)\)

而這個unordered_map,是c++ 11的新特性,是一個\(key\)

無序的map,其內部是用雜湊表實現的,單次查詢的複雜度可以達到\(O(1)\)

於是就是程式碼了

#include<iostream>
#include<map>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<tr1/unordered_map>
//unordered_map所需的標頭檔案,同時在名稱空間里加::tr1
#define re register
#define maxn 100001
#define gc getchar
using namespace std::tr1;
using namespace std;
unordered_map<int,int> s;
int fa[maxn<<2],tot[maxn<<2],sum[maxn<<2];
//因為我們虛點的是要用到n+1,n+2...的,所以多開幾倍空間
int n,m;
inline int read()
{
    char c=gc();
    int dx=0;
    while(c<'0'||c>'9') c=gc();
    while(c>='0'&&c<='9')
      dx=(dx<<3)+(dx<<1)+c-48,c=gc();
    return dx;
}
int find(int x)
{
    if(x==fa[x]) return fa[x];
    if(s.count(fa[x])) fa[x]=s[fa[x]];
    //如果一個元素的代表元素已經被刪除了,那麼我們就把它的代表元素換成其對映的那個虛點
    return fa[x]=find(fa[x]);
}
void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10+48);
}
int main()
{  
    while(scanf("%d%d",&n,&m)!=EOF)//多組資料標配
    {
        s.clear();
        for(re int i=1;i<=n;i++)
            fa[i]=sum[i]=i,tot[i]=1;
        while(m--)
        {
            int p=read();
            if(p==1)
            {
                int xx=find(read());
                int yy=find(read());
                if(xx==yy) continue;
                tot[xx]+=tot[yy];
                sum[xx]+=sum[yy];
                fa[yy]=xx;
                tot[yy]=sum[yy]=0;//常規合併操作
            }
            if(p==2)
            {
                int x=read();
                int xx=find(x);
                int yy=find(read());
                if(xx==yy) continue;
                if(fa[x]==x&&tot[x]==1)
                {
                    tot[x]=0;
                    sum[x]=0;
                    fa[x]=yy;
                    tot[yy]+=1;
                    sum[yy]+=x;
                    continue;
                }//如果這個元素代表元素是它自己,且元素個數為1,那它就不可能是其他元素的代表元素,於是我們直接fa[x]=yy就好了
                if(s.find(x)==s.end())
                {
                    s[x]=++n;
                    if(xx==x) fa[n]=n;
                    else fa[n]=xx;//如果這個元素代表元素是它自己,那麼虛點繼承這個狀態的時候,讓虛點的代表元素也是它自己就好了
                    tot[n]=tot[xx]-1;
                    sum[n]=sum[xx]-x;
                    tot[yy]+=1;
                    sum[yy]+=x;//向新集合新增x
                    tot[xx]-=1;
                    sum[xx]-=x;//從原集合中刪除x
                    if(xx==x) sum[xx]=tot[xx]=0;
                    fa[x]=yy;
                }else fa[x]=yy,sum[xx]-=x,tot[xx]-=1,tot[yy]+=1,sum[yy]+=x;
    //如果這個元素已經被對映過了,即它已經被加入某一個集合,所以這個時候它不可能成為代表元素,於是這個時候我們也可以直接進行刪除和合並的操作
            }
            if(p==3)
            {
                int xx=find(read());
                write(tot[xx]),putchar(' '),write(sum[xx]);
                putchar(10);
            }
        }
    }
    return 0;
}