Living-Dream 系列笔记 第34期
T1
有一个比较秒的 trick:虚拟点。
对于本题,我们设一虚拟点 \(n+1\) 表示水源,于是打井的操作即为与点 \(n+1\) 连边,将点权转为边权。
然后跑 kruskal 即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,tot;
int fa[331];
int w[331];
int p[331][331];
struct E{
	int u,v,w;
}e[90031];
bool cmp(E x,E y){
	return x.w<y.w;
}
int fnd(int x){
	if(fa[x]==x) return x;
	return fa[x]=fnd(fa[x]);
}
void mrg(int x,int y){
	x=fnd(x),y=fnd(y);
	fa[x]=y;
}
int kruskal(){
	sort(e+1,e+tot+1,cmp);
	int cnt=0,ans=0;
	for(int i=1;i<=tot;i++){
		if(fnd(e[i].u)!=fnd(e[i].v)){
			mrg(e[i].u,e[i].v);
			ans+=e[i].w;
		}
	}
	return ans;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n+1;i++) fa[i]=i;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>p[i][j];
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			tot++,e[tot].u=i,e[tot].v=j,e[tot].w=p[i][j];
	for(int i=1;i<=n;i++)
		tot++,e[tot].u=i,e[tot].v=n+1,e[tot].w=w[i];
	cout<<kruskal();
	return 0;
}
T2
有点意思的一道题。
考虑朴素做法:
通过观察可以发现最小生成树每加一条边就会成环,
为使最小生成树不被破坏,新加边的边权一定严格大于环上所有边的边权,
于是我们枚举点对并 dfs 找环取 \(\max\) 连边即可,
时间复杂度 \(O(n^3)\),套个 LCA 可以降至 \(O(n^2)\),可以拿 50 pts。
我们发现朴素做法难以继续优化,
于是我们转换研究对象,由点至边,
假定最小生成树本来并未连边,
考虑按边权从小到大连接最小生成树的每一条边,
连边时合并两端的集合并维护两端集合的大小,
因为要构成完全图,所以两端集合中的点要两两连边,
这些边为不使最小生成树被破坏,又要保证最小,
于是它们的边权可以均设为当前树边的边权 \(+1\)。
因此两端点集合对于答案的贡献即为
\[(sz_x+sz_y-1) \times (w_i+1)+w_i
\]
(\(sz_x\)、\(sz_y\) 为两端集合大小,\(w_i\) 为当前树边边权)
直接累加所有贡献即为答案。时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans;
int fa[100031],sz[100031];
struct E{
	int u,v,w;
}e[100031];
bool cmp(E x,E y){
	return x.w<y.w;
}
int fnd(int x){
	return (fa[x]==x?x:fa[x]=fnd(fa[x]));
}
void mrg(int x,int y){
	fa[x]=y,sz[y]+=sz[x];
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
	for(int i=1;i<n;i++) cin>>e[i].u>>e[i].v>>e[i].w;
	sort(e+1,e+n,cmp);
	for(int i=1;i<n;i++){
		int x=fnd(e[i].u),y=fnd(e[i].v);
		ans+=(sz[x]*sz[y]-1)*(e[i].w+1)+e[i].w;
		mrg(x,y);
	}
	cout<<ans;
	return 0;
}
T3
见 TJ。
T4
建图,边权为异或值,跑最大生成树即可。
upd:
- 
关于为什么能跑最大生成树的原因: 考虑淘汰赛的过程,容易发现 \(n\) 支队伍总会经过 \(n-1\) 场比赛决出胜者,它们可以构成一棵树。 我们考虑在已知胜者的情况下,将胜者节点提出作为这棵树的根节点,那么比赛的过程便可以由下至上地表示出来。 又因为题目要求最大的异或和,于是跑最大生成树即可。 
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,tot;
int a[2031];
int fa[2031];
struct E{
	int u,v,w;
}e[4000031];
bool cmp(E x,E y){
	return x.w>y.w;
}
int fnd(int x){
	return (fa[x]==x?x:fa[x]=fnd(fa[x]));
}
void mrg(int x,int y){
	x=fnd(x),y=fnd(y);
	if(x!=y) fa[x]=y;
}
int kruskal(){
	int ans=0,cnt=0;
	for(int i=1;i<=tot;i++){
		if(fnd(e[i].u)!=fnd(e[i].v)){
			mrg(e[i].u,e[i].v);
			ans+=e[i].w,cnt++;
			if(cnt==n-1) return ans;
		}
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],fa[i]=i;
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			e[++tot].u=i,e[tot].v=j,e[tot].w=a[i]^a[j];
	sort(e+1,e+tot+1,cmp);
	cout<<kruskal();
	return 0;
}
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号