2024牛客多校2B MST

同步发布于我的网站

Problem

Sajin最近深入研究了最小生成树,现在他已经掌握了MST的算法。他渴望通过一系列查询来评估您对最小生成树概念的掌握程度。

您将面临一个加权无向图,该图包含没有任何自环的 \(n\) 个顶点和 \(m\) 条边。

Sajin提出 \(q\) 询问。对于每个顶点集,都给出了一个顶点集 \(S\) 。您的目标是确定 \(S\)诱导子图(induced subgraph)并找到其最小生成树的权重。如果 \(S\) 的诱导子图断开,则输出-1

图的诱导子图是另一个图,由图的顶点子集和原始图中的所有边组成,连接该子集中的顶点对。即,对于图 \(G=(V,E)\) ,给定 \(V^\prime\) ,则有 \(E^\prime=\{(u,v) \mid u,v\in V^\prime,(u,v)\in E\}\),诱导子图为 \(G^\prime=(V^\prime,E^\prime)\)

\(2\le n\le 10^5,1\le m,q\le10^5\)

\(1 \le |S_i|\le n,\sum S_i\le 10^5\)

Solution

Algorithm1

由于限制了 \(\sum S_i\le 10^5\) , MST本身的时间复杂度是完全过得去的。但是瓶颈在于如何从 \(G\) 中挑出需要的边。

考虑两种找边的方法:

  1. 对于 \(S\) 中的点 \(x\),遍历 \(x\) 的所有出边,终点为 \(y\) 。如果 \(y\in S\) ,那么边 \((x,y)\in G^\prime\) 。将得到的所有边存起来并排序跑kruskal。

​ 对于一次询问,这个算法的最坏时间复杂度为 \(O(m\log(|S|))\) ,即所有边都需要被遍历一次。

  1. 用map存图 \(G\) ;双重遍历 \(x,y\in S\) ,将其中存在的边 \((x,y)\) 取出来并排序跑kruskal。

​ 整体时间复杂度是\(O(n^2+n^2\log(n))\),即双重遍历和kruskal。

\(base=\sqrt n\)

  • \(|S|\le base\) 时使用第二种算法,总时间复杂度为 \(O(n\cdot base\cdot \log(n))\)
  • \(|S| > base\) 时使用第一种算法,总时间复杂度为 \(O(base\cdot m\cdot \log(n))\)

Algorithm2

一般建立最小生成树,我们会使用上面的第一种找边的方式:对于 \(S\) 中的点 \(x\),遍历 \(x\) 的所有出边,终点为 \(y\) 。如果 \(y\in S\) ,那么边 \((x,y)\in G^\prime\) 。将得到的所有边存起来并排序跑kruskal。

问题的瓶颈在于新建导出子图 \(G^\prime\) 的时候,会被类菊花图卡掉。可能会一直询问类菊花图的核心点,而这些核心点具有很多条出边,每次询问都需要遍历核心节点的出边。

考虑类似三元环的连边方式,对于每条边,只从度小的点到度大的点连一条单向边。如果度数相同,则从编号小的连接到编号大的。

在这样构建的图中,每个点的出度不会大于 \(\sqrt m\) 。如果有某个点的出度为 \(d > \sqrt m\) ,那么在原图中,需要有 \(d\) 个度数大于 \(d\) 的节点与该节点连接,此时总边数至少 \(d\times d>m\)​,矛盾。

所以可以在这样的一张单向图中放心的找边,单次询问时间复杂度 \(O(|S| \sqrt m \log(|S|))\)​ 。

Code

Code1

#define N 100010
struct Edge
{
	int x,y;
	LL w;
	Edge(int xx,int yy,LL ww)
	{
		x=xx;
		y=yy;
		w=ww;
	}
	bool operator<(const Edge & z)
	{
		return w<z.w;
	}
};

int n,m,q;

namespace algo1{
	int cnt;
	int head[N],nxt[N*2],ver[N*2],w[N*2];
	void insert(int x,int y,LL z)
	{
		nxt[++cnt]=head[x];
		head[x]=cnt;
		ver[cnt]=y;
		w[cnt]=z;
	}
	
};

namespace algo2{
	map<pii,LL>mp;
	void insert(int x,int y,LL z)
	{
		pii e=make_pair(x,y);
		if(mp.find(e)==mp.end()) mp[e]=z;
		else mp[e]=min(mp[e],z);
	}
	bool exist(pii e)
	{
		return mp.find(e)!=mp.end();
	}
};

namespace DSU{
	int vis[N],f[N],sz[N];
	
	int getf(int x)
	{
		if(x==f[x]) return x;
		return f[x]=getf(f[x]);
	}
	
	void merge(int x,int y)
	{
		x=getf(x);
		y=getf(y);
		if(x==y) return;
		if(sz[x]<sz[y]) swap(x,y);
		f[y]=x;
		sz[x]+=sz[y];
	}
	
	bool ask(int x,int y)
	{
		return getf(x)==getf(y);
	}
};

LL MST(vector<int>V,vector<Edge>E)
{
	sort(E.begin(),E.end());
	LL ans=0;
	int cnt=1;
	for(unsigned int i=0;i<E.size();i++)
	{
		if(!DSU::ask(E[i].x,E[i].y))
		{
			cnt++;
			DSU::merge(E[i].x,E[i].y);
			ans+=E[i].w;
		}
	}
	if(cnt!=V.size()) return -1;
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cout.precision(10);
	int t=1;
//	cin>>t;
	while(t--)
	{
		cin>>n>>m>>q;
		for(int i=1;i<=m;i++)
		{
			int x,y;
			LL z;
			cin>>x>>y>>z;
			algo1::insert(x,y,z);
			algo1::insert(y,x,z);
			algo2::insert(x,y,z);
			algo2::insert(y,x,z);
		}
		const int base=sqrt(100000);
		while(q--)
		{
			int k;
			cin>>k;
			vector<int>S;
			vector<Edge>E;
			for(int i=0;i<k;i++)
			{
				int t;cin>>t;
				S.push_back(t);
				DSU::vis[t]=1;
				DSU::sz[t]=1;
				DSU::f[t]=t;
			}
			if(k>base)
			{
				//algo1
				for(int i=0;i<k;i++)
				{
					int x=S[i];
					for(int j=algo1::head[x];j;j=algo1::nxt[j])
					{
						int y=algo1::ver[j];
						LL w=algo1::w[j];
						if(DSU::vis[y])
						{
							E.push_back(Edge(x,y,w));
						}
					}
				}
			}
			else
			{
				//algo2
				for(int i=0;i<k;i++)
				{
					for(int j=i+1;j<k;j++)
					{
						pii e=make_pair(S[i],S[j]);
						if(algo2::exist(e))
						{
							E.push_back(Edge(S[i],S[j],algo2::mp[e]));
						}
					}
				}
			}
			
			cout<<MST(S,E)<<endl;
			
			for(int i=0;i<k;i++)
			{
				DSU::vis[S[i]]=0;
			}
		}
	}
	return 0;
}

生最差劲的一件事莫过于早上写的代码但半路跑路还没做任何标记,晚上打开不知道从何续写。

人生最美好的一件事莫过于发现这份代码其实是以及写完的!?而且还能过样例!??而且直接提交直接过了!???

Code2

#define N 100010
struct Edge
{
	int x,y;
	LL w;
	Edge(int xx,int yy,LL ww)
	{
		x=xx;
		y=yy;
		w=ww;
	}
	bool operator<(const Edge & z)
	{
		return w<z.w;
	}
};

int n,m,q;


namespace G1
{
	map<pii,LL>mp;
	int degree[N];
	void insert(int x,int y,LL z)
	{
		pii e=make_pair(x,y);
		if(mp.find(e)==mp.end()) mp[e]=z;
		else mp[e]=min(mp[e],z);
		degree[x]++;
	}
};

namespace G2
{
	int cnt;
	int head[N],nxt[N*2],ver[N*2];
	LL w[N];
	void insert(int x,int y,LL z)
	{
		nxt[++cnt]=head[x];
		head[x]=cnt;
		ver[cnt]=y;
		w[cnt]=z;
	}
};

namespace DSU{
	int vis[N],f[N],sz[N];
	
	int getf(int x)
	{
		if(x==f[x]) return x;
		return f[x]=getf(f[x]);
	}
	
	void merge(int x,int y)
	{
		x=getf(x);
		y=getf(y);
		if(x==y) return;
		if(sz[x]<sz[y]) swap(x,y);
		f[y]=x;
		sz[x]+=sz[y];
	}
	
	bool ask(int x,int y)
	{
		return getf(x)==getf(y);
	}
};


LL MST(vector<int>V,vector<Edge>E)
{
	sort(E.begin(),E.end());
	LL ans=0;
	int cnt=1;
	for(unsigned int i=0;i<E.size();i++)
	{
		if(!DSU::ask(E[i].x,E[i].y))
		{
			cnt++;
			DSU::merge(E[i].x,E[i].y);
			ans+=E[i].w;
		}
	}
	if(cnt!=V.size()) return -1;
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cout.precision(10);
	int t=1;
//	cin>>t;
	while(t--)
	{
		cin>>n>>m>>q;
		for(int i=1;i<=m;i++)
		{
			int x,y;
			LL z;
			cin>>x>>y>>z;
			G1::insert(x,y,z);
			G1::insert(y,x,z);
		}
		
		for(auto it=G1::mp.begin();it!=G1::mp.end();it++)
		{
			int x=it->first.first,y=it->first.second;
			LL w=it->second;
			if(G1::degree[x]<G1::degree[y]||(G1::degree[x]==G1::degree[y]&&x<y)) G2::insert(x,y,w);
		}
		
		while(q--)
		{
			int k;
			cin>>k;
			vector<int>S;
			vector<Edge>E;
			for(int i=0;i<k;i++)
			{
				int t;cin>>t;
				S.push_back(t);
				DSU::vis[t]=1;
				DSU::sz[t]=1;
				DSU::f[t]=t;
			}
			for(int i=0;i<k;i++)
			{
				int x=S[i];
				for(int j=G2::head[x];j;j=G2::nxt[j])
				{
					int y=G2::ver[j];
					LL w=G2::w[j];
					if(DSU::vis[y])
					{
						E.push_back(Edge(x,y,w));
					}
				}
			}
			
			cout<<MST(S,E)<<endl;
			
			for(int i=0;i<k;i++)
			{
				DSU::vis[S[i]]=0;
			}
		}
		
		
	}
	return 0;
}
posted @ 2024-07-20 23:59  Vanilla_chan  阅读(8)  评论(0编辑  收藏  举报