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;
}
posted @ 2019-01-02 12:07  asuldb  阅读(221)  评论(0编辑  收藏  举报