生成树和笛卡尔树专题
A. [COCI2009-2010#7] SVEMIR
这个题其实没啥,没看题解,自己口胡的做法,就直接把x,y,z三个方向排序当成一个边,总边数就是3n-3吧,然后就是朴素的Kruskal,直接跑完即可。
Code
#include<bits/stdc++.h>
using namespace std;
int n,cnt,ans,cntt;
const int N=1e5+5;
struct point{
int x,y,z,id;
}a[N];
struct edge{
int u,v,w;
}e[N*3];
int fa[N];
bool cmp1(point a,point b){
return a.x<b.x;
}
bool cmp2(point a,point b){
return a.y<b.y;
}
bool cmp3(point a,point b){
return a.z<b.z;
}
bool cmp(edge a,edge b){
return a.w<b.w;
}
int find(int x){
if(fa[x]==x)
return x;
else
return fa[x]=find(fa[x]);
}
int main(){
//首先任何两点之间都可以直接建造
//目标是建造出一颗树
//直接考虑优化边数
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].x>>a[i].y>>a[i].z,a[i].id=i;
sort(a+1,a+n+1,cmp1);
for(int i=2;i<=n;i++)
e[++cnt].u=a[i-1].id,e[cnt].v=a[i].id,e[cnt].w=a[i].x-a[i-1].x;
sort(a+1,a+n+1,cmp2);
for(int i=2;i<=n;i++)
e[++cnt].u=a[i-1].id,e[cnt].v=a[i].id,e[cnt].w=a[i].y-a[i-1].y;
sort(a+1,a+n+1,cmp3);
for(int i=2;i<=n;i++)
e[++cnt].u=a[i-1].id,e[cnt].v=a[i].id,e[cnt].w=a[i].z-a[i-1].z;
sort(e+1,e+cnt+1,cmp);
// for(int i=1;i<=cnt;i++){
// cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
// }
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=cnt;i++){
int x=find(e[i].u),y=find(e[i].v);
if(x==y)
continue;
fa[x]=y;
ans+=e[i].w;
cntt++;
if(cntt==n-1)
break;
}
cout<<ans<<"\n";
}
B. [SCOI2012] 滑雪
听说很多人用的都是Prim,但是显然也可以用Kruskal,对于第一问来讲,就是一个从高向低直接跑个dfs/bfs即可
对于第二问,可以将第一问的大法师中走过的边直接选到一个边集中,然后跑一遍Kruskal。边的排序以高度为第一关键词,边权为第二关键词。这样就能保证拓展的点最多,进而再用最小生成树求最短距离。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,cnt,cntt,ans,anss;
const int N=1e5+5;
int h[N],fa[N];
struct Edge{
int u,v,w;
}edge[N*20],edge1[N*20];
bool cmp(Edge a,Edge b){
if(h[a.v]!=h[b.v])
return h[a.v]>h[b.v];
return a.w<b.w;
}
vector<int> e[N],e2[N];
bool flag[N];
int find(int x){
if(x==fa[x])
return x;
else
return fa[x]=find(fa[x]);
}
void dfs(int x,int faa){
if(flag[x]==0)
anss++;
flag[x]=1;
for(int i=0;i<e[x].size();i++){
edge1[++cntt].u=x;
edge1[cntt].v=e[x][i];
edge1[cntt].w=e2[x][i];
if(flag[e[x][i]])
continue;
dfs(e[x][i],x);
}
}
signed main(){
//只能走向比自己低的点
//题目相当于构建一棵最小生成树
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>h[i];
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
if(h[u]==h[v]){
edge[++cnt].u=u,edge[cnt].v=v;
edge[cnt].w=w;
edge[++cnt].u=v,edge[cnt].v=u;
edge[cnt].w=w;
}
if(h[u]<h[v]){
edge[++cnt].u=v,edge[cnt].v=u;
edge[cnt].w=w;
}
if(h[u]>h[v]){
edge[++cnt].u=u,edge[cnt].v=v;
edge[cnt].w=w;
}
}
for(int i=1;i<=cnt;i++){
e[edge[i].u].push_back(edge[i].v);
e2[edge[i].u].push_back(edge[i].w);
}
dfs(1,0);
cout<<anss<<" ";
for(int i=1;i<=n;i++)
fa[i]=i;
sort(edge1+1,edge1+cntt+1,cmp);
for(int i=1;i<=cnt;i++){
int x=find(edge1[i].u),y=find(edge1[i].v);
if(x==y)
continue;
fa[y]=x;
ans+=edge1[i].w;
cntt++;
if(cntt==anss-1)
break;
}
cout<<ans<<"\n";
}
C. [USACO01OPEN] Earthquake
观察数据发现n \(\le\) 400,跑一次的最小生成树带来的时间复杂度的负担显然是很小的,此时时间复杂度为O(nlogn),显然完全够一个二分答案的时间复杂度,答案的值域在[0,f]之间,而答案需要保留四位小数,可以直接拿小数跑,也可以将答案乘以10000跑整数。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,f;
const int N=1e4+5;
const double eps=1e-9;
int c[N],t[N],u[N],v[N],fa[405];
struct edge{
int u,v;
double w;
}e[N];
bool cmp(edge a,edge b){
return a.w<b.w;
}
int find(int x){
if(fa[x]==x)
return x;
else
return fa[x]=find(fa[x]);
}
bool check(double x){
double xx=x,sum=0;
int cntt=0;
for(int i=1;i<=m;i++){
e[i].u=u[i],e[i].v=v[i];
e[i].w=1.0*(xx*t[i]+c[i]);
}
// for(int i=1;i<=m;i++)
// cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++){
int x=find(e[i].u),y=find(e[i].v);
if(x==y)
continue;
fa[y]=x;
cntt++;
sum+=e[i].w;
if(cntt==n-1)
break;
}
if(f>sum)
return false;
else
return true;
}
int main(){
cin>>n>>m>>f;
for(int i=1;i<=m;i++)
cin>>u[i]>>v[i]>>c[i]>>t[i];
double l=0,r=f,mid;
while(r>l+eps){
mid=(l+r)/2;
if(check(mid))
r=mid-eps;
else
l=mid+eps;
}
cout<<fixed<<setprecision(4)<<l<<"\n";
}
D. [蓝桥杯 2023 省 A] 网络稳定性
最大生成树之后直接跑\(Lca\)即可
Code
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
int n,m,q,u,v,fa[N],a[N],f[N][20],dep[N];
bool vis[N];
vector<int> e[N];
struct edge{
int u,v,w;
}e1[N];
int find(int x){
if(fa[x]==x)
return x;
else
return fa[x]=find(fa[x]);
}
bool cmp(edge a,edge b){
return a.w>b.w;
}
void dfs(int x,int fa){
vis[x]=1;
f[x][0]=fa;
dep[x]=dep[fa]+1;
for(int i=0;i<e[x].size();i++){
if(e[x][i]==fa)
continue;
dfs(e[x][i],x);
}
}
int lca(int u,int v){
if(dep[u]<dep[v])
swap(u,v);
int d=dep[u]-dep[v];
for(int i=19;i>=0;--i)
if(d>>i&1)
u=f[u][i];
if(u==v)
return u;
for(int i=19;i>=0;--i)
if(f[u][i]!=f[v][i])
u=f[u][i],v=f[v][i];
return f[u][0];
}
int main(){
cin>>n>>m>>q;
for(int i=1;i<=n+m;i++)
fa[i]=i;
for(int i=1;i<=m;++i)
cin>>e1[i].u>>e1[i].v>>e1[i].w;
sort(e1+1,e1+m+1,cmp);
int cur=n;
for(int i=1;i<=m;++i){
int u=e1[i].u,v=e1[i].v,w=e1[i].w;
u=find(u),v=find(v);
if(u==v)
continue;
cur+=1;
fa[u]=fa[v]=cur;
e[cur].push_back(u);
e[cur].push_back(v);
a[cur]=w;
}
for(int i=cur;i>=1;i--)
if(!vis[i])
dfs(i,0);
for(int j=1;j<20;++j)
for(int i=1;i<=cur;++i)
f[i][j]=f[f[i][j-1]][j-1];
while(q--){
cin>>u>>v;
if(find(u)!=find(v)){
puts("-1");
continue;
}
cout<<a[lca(u,v)]<<"\n";
}
return 0;
}
E. [THUPC2022 初赛] 最小公倍树
想了很久,无果,直接看了题解,发现建边方式挺妙的,就是按照枚举公因数的方式建边,设在[l,r]中是x的倍数最小的数为w将所有是x的倍数的数和w建边最优,所以跑Kruskal即可。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int l,r,cnt,fa[N],cntt;
#define ll long long
ll ans;
struct edge{
int u,v;
ll w;
}e[1300005];
bool cmp(edge x,edge y){
return x.w<y.w;
}
int find(int x){
if(fa[x]==x)
return x;
else
return fa[x]=find(fa[x]);
}
int main(){
//其实感觉最后一步就是直接跑一个最小生成树
//但是要看具体怎么建边
//(4,6) (4,8) (4,10) (4,12)
//(3,6) (3,9) (3,12)
//(4,8) (4,12)
//(5,10)
//(6,12)
//(4,6) (4,8) (4,10) (4,12) (3,6) (3,9) (3,12)
//(5,10) (6,12)
cin>>l>>r;
for(int i=1;i<=r;i++){
int k=(l-1)/i+1;
int zuo=k*i;
for(int j=zuo+i;j<=r;j+=i){
e[++cnt].u=zuo;
e[cnt].v=j;
e[cnt].w=1ll*j*zuo/i;
}
}
sort(e+1,e+cnt+1,cmp);
// for(int i=1;i<=cnt;i++){
// cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
// }
for(int i=l;i<=r;i++)
fa[i]=i;
for(int i=1;i<=cnt;i++){
int x=find(e[i].u),y=find(e[i].v);
if(x==y)
continue;
cntt++;
fa[y]=x;
ans+=e[i].w;
if(cntt==r-l)
break;
}
cout<<ans<<"\n";
}

浙公网安备 33010602011771号