codeforces1559 D2. Mocha and Diana (Hard Version)(并查集+启发式合并+随机化)

这篇博客探讨了图的连通块问题,通过Dijkstra's Algorithm和并查集(dsu)的数据结构解决。文章详细解释了如何在两个已连接的图中寻找可以连边的节点,以构建一棵树。同时,介绍了使用随机化策略进行连边的选择,分析了成功率,并给出了C++实现代码。涉及的技术包括图论、并查集、随机算法和概率计算。
摘要由CSDN通过智能技术生成

D2. Mocha and Diana (Hard Version)

RunningBeef题解
首先将图1的点与1号点所在的连通块相连,图2类似。

然后就是在图1和图2中选择没有和1号点在同一个连通块的点,能连边就连。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}

const int N=100010;

int n,m1,m2;
struct dsu 
{
	vector<int> fa;
	dsu(int n):fa(n){iota(fa.begin(),fa.end(),0);}
	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
	void merge(int x,int y)
	{
	    x=find(x),y=find(y);
	    if(x>y) swap(x,y);
	    fa[y]=x;
	}
}; 
int main()
{
    n=rd(),m1=rd(),m2=rd();
    dsu t1(n+1),t2(n+1);
    while(m1--)
    {
        int u=rd(),v=rd();
        t1.merge(u,v);
    }
    while(m2--)
    {
        int u=rd(),v=rd();
        t2.merge(u,v);
    }
    vector<int> v1,v2;
    vector<pair<int,int>> ans;
    for(int i=2;i<=n;i++) 
    {
        if(t1.find(i)!=1&&t2.find(i)!=1) 
        {
        	ans.push_back({1,i});
        	t1.merge(1, i);
        	t2.merge(1, i);
        }
        if(t1.find(i)!=1)v1.push_back(i);
        if(t2.find(i)!=1)v2.push_back(i);
	}
    while(!v1.empty()&&!v2.empty()) 
    {
    	if(t1.find(v1.back())==1&&t2.find(v1.back())==1) 
    	{
        	v1.pop_back();
        	continue;
        }   
    	if(t1.find(v2.back())==1&&t2.find(v2.back())==1)
    	{
        	v2.pop_back();
        	continue;
    	}
        ans.push_back({v1.back(),v2.back()});
        t1.merge(v1.back(),v2.back());
        t2.merge(v1.back(),v2.back());
    	v1.pop_back();v2.pop_back();
    } 
    printf("%d\n",(int)ans.size());
    for(auto t:ans) printf("%d %d\n",t.first,t.second);
}
Code2

晚上刷b站刷到neal大神,发现这个随机做法很吊,于是写一下,顺便学习下pb_ds

n \color{black}\text n n eal \color{red}\text {eal} eal大神的做法

首先将图1连边后变成若干个连通块,同样将图2连边后也变成若干个连通块。
最终能够连边的数量一定是让某个图变成一棵树。于是对于连边的答案数量是固定的。

对于每次连边,我们随机从图一或者图二中随机随机选择某个连通块的某两个点,看看它们是否能够连边,如果能就连上,就这样随机连边。

yy一下感觉每次连边成功的概率非常大。why?

考虑冲突的概率:假设图1中有x个连通块,图2中有y个连通块

不冲突的大致概率 1 − 1 x 2 − 1 y 2 + cst 1-\frac{1}{x^2}-\frac{1}{y^2}+\text{cst} 1x21y21+cst

图一冲突或者图二冲突。
cst \text{cst} cst根据容斥原理在两个图中都冲突的概率。

x或y都必须大于1,于是概率会大于 1 2 \frac{1}{2} 21,已经比较大了。


需要维护并查集有哪些点,可以用个vector<int> lis维护,合并的时候启发式合并。

维护连通块需要用pb_ds库,我们需要快速find_by_order,并且支持快速插入删除,需要平衡树。

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
using ll=long long;

template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
template<typename T>
using ordered_set = tree<T,null_type,less<T>,rb_tree_tag,tree_order_statistics_node_update>;

const int N=100010;

int n,m1,m2;
struct dsu 
{
	vector<int> fa;
	vector<vector<int>> lis;
	int cnt; //连通块的数量
	dsu(int n)
	{
	    fa.resize(n);
	    lis.resize(n);
	    cnt=n-1;
	    for(int i=0;i<n;i++) fa[i]=i,lis[i]={i};
	}
	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
	bool merge(int x,int y)
	{
	    x=find(x),y=find(y);
	    if(x==y) return false;
	   
	    if(lis[x].size()<lis[y].size()) swap(x,y);
	 
	    lis[x].insert(lis[x].end(),lis[y].begin(),lis[y].end());
	    lis[y].clear();
	    fa[y]=x;
	    cnt--;
	    return true;
	}
}; 

std::mt19937 rnd(233);
int main()
{
    n=rd(),m1=rd(),m2=rd();
    dsu t1(n+1),t2(n+1);
    while(m1--)
    {
        int u=rd(),v=rd();
        t1.merge(u,v);
    }
    while(m2--)
    {
        int u=rd(),v=rd();
        t2.merge(u,v);
    }
    int need=min(t1.cnt,t2.cnt)-1;
    ordered_set<int> rt1,rt2;
    
    for(int i=1;i<=n;i++)
    {
        if(t1.find(i)==i) rt1.insert(i);
        if(t2.find(i)==i) rt2.insert(i);
    }
    // 随机从图中的某个连通块找某个点
    auto get_random=[&](dsu &t,ordered_set<int> &rt)->int{
        int u=*rt.find_by_order((rnd()%rt.size()+rt.size())%rt.size());
        return t.lis[u][(rnd()%t.lis[u].size()+t.lis[u].size())%t.lis[u].size()];
    };
    // 随机找一个图
    auto get_random_node=[&]()->int{
        if(rnd()%2==0) 
            return get_random(t1,rt1);
        else 
            return get_random(t2,rt2);
    };
    
    vector<pair<int,int>> ans;
    while(ans.size()<need)
    {
    	// 随机出两点 a b看看是否能够连边
        int a=get_random_node();
        int b=get_random_node();
        if(t1.find(a)!=t1.find(b)&&t2.find(a)!=t2.find(b))
        {
            ans.push_back({a,b});
            rt1.erase(rt1.find(t1.find(a)));
            rt1.erase(rt1.find(t1.find(b)));
            rt2.erase(rt2.find(t2.find(a)));
            rt2.erase(rt2.find(t2.find(b)));
            t1.merge(a,b);
            t2.merge(a,b);
            rt1.insert(t1.find(a));
            rt2.insert(t2.find(a));
        }
    }
    printf("%d\n",(int)ans.size());
    for(auto t:ans) printf("%d %d\n",t.first,t.second);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值