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\) 为两端集合大小,\(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;
}