Living-Dream 系列笔记 第34期

Posted on 2024-03-02 17:00  _XOFqwq  阅读(5)  评论(0编辑  收藏  举报

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;
}