最小生成树

最小生成树

Kruskal算法O(mlogm)

按边扫描,选边加入树,使每个子树都最小,一边加边一边合并,最后得到整棵树。

1.建立并查集,每个点个字构成一个集合。

2.将所有边按照w大小排序,依次扫描每条边(u,v,w)。

3.如果u,v属于同一集合(即相连,存在长度更小的使u和v相连的边),则跳过。

4.如果u,v不属于同一集合(即未相连,本边是使u和v相连的最小边),则合并集合,ans+=w。

5.所有边扫描完毕,在第四步运行的所有边即为该树的所有边,ans即是最小边权值总和。

struct t_
{
	int u,v,w;
}t[N];
int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}
bool cmp(t_ a,t_ b)
{
	return a.w<b.w;
}
void Kruskal()
{
	sort(t+1,t+m+1,cmp);

	for(int i=1;i<=m;++i)
	{
		int x=find(t[i].u),y=find(t[i].v),w=t[i].w;
		
		if(x==y) continue;

		ans+=w;
		f[y]=x;
	}
}

走廊泼水节

给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。

求增加的边的权值总和最小是多少。

注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。

输入格式

第一行包含整数t,表示共有t组测试数据。

对于每组测试数据,第一行包含整数N。

接下来N-1行,每行三个整数X,Y,Z,表示X节点与Y节点之间存在一条边,长度为Z。

输出格式

每组数据输出一个整数,表示权值总和最小值。

每个结果占一行。

数据范围

1≤N≤60001≤N≤6000
1≤Z≤1001≤Z≤100

输入样例:

2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5 

输出样例:

4
17

由Kruskal可轻易想到,本题相当于删除了所有多余的连接u集合和v集合的边,只要我们每次合并u和v时,将多余的连接u集合,v集合的边还原即可。因为要保证wu,v是最短边,边的长度为整数且最小,所以我们先将所有的边按长度从小到大排序,每次扫描到一条边时进行合并u,v和还原剩余边的操作,还原的边长度为wu,v+1,则\(ans+=(w~u,v~+1)(siz[x]*siz[y]-1)\)

#include<bits/stdc++.h>
using namespace std;

const int N=6e3+5;
int tt,n,ans,te,f[N],siz[N];
struct t_
{
	int u,v,w;
}t[N];

bool cmp(t_ a,t_ b)
{
	return a.w<b.w;
}

int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}
int main()
{
	scanf("%d",&tt);
	while(tt--)
	{
		ans=te=0;
		scanf("%d",&n);
		for(int i=1,u,v,w;i<n;++i)
		{
			f[i]=i;
			siz[i]=1;
			scanf("%d %d %d",&u,&v,&w);
			t[++te]=(t_){u,v,w};
		}
		f[n]=n;
		siz[n]=1;
		
		sort(t+1,t+n,cmp);
		
		for(int i=1;i<n;++i)
		{
			int x=find(t[i].u),y=find(t[i].v),w=t[i].w;
			ans+=(w+1)*(siz[x]*siz[y]-1);
			f[y]=x;
			siz[x]+=siz[y];
		}
		
		printf("%d\n",ans);
	}
	
	
}

野餐规划

一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界。

现在他们想要去公园玩耍,但是他们的经费非常紧缺。

他们将乘车前往公园,为了减少花费,他们决定选择一种合理的乘车方式,可以使得他们去往公园需要的所有汽车行驶的总公里数最少。

为此,他们愿意通过很多人挤在同一辆车的方式,来减少汽车行驶的总花销。

由此,他们可以很多人驾车到某一个兄弟的家里,然后所有人都钻进一辆车里,再继续前进。

公园的停车场能停放的车的数量有限,而且因为公园有入场费,所以一旦一辆车子进入到公园内,就必须停在那里,不能再去接其他人。

现在请你想出一种方法,可以使得他们全都到达公园的情况下,所有汽车行驶的总路程最少。

输入格式

第一行包含整数n,表示人和人之间或人和公园之间的道路的总数量。

接下来n行,每行包含两个字符串A、B和一个整数L,用以描述人A和人B之前存在道路,路长为L,或者描述某人和公园之间存在道路,路长为L。

道路都是双向的,并且人数不超过20,表示人的名字的字符串长度不超过10,公园用“Park”表示。

再接下来一行,包含整数s,表示公园的最大停车数量。

你可以假设每个人的家都有一条通往公园的道路。

输出格式

输出“Total miles driven: xxx”,其中xxx表示所有汽车行驶的总路程。

输入样例:

10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 43
Bernardo Park 19
Bernardo Clemenzi 82
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 109
Park Herb 24
Herb Eduardo 79
3

输出样例:

Total miles driven: 183

精简版题意:给出n条边,求以节点1为根,根节点入度不超过s的最小生成树。

1.删除1节点,剩下的点可形成k个联通块。

2.求出每个联通块内部的最小生成树,即求出节点1的每个最小子树,在每个子树中选出一个点与1相连,使w1,v最小。

3.此时还剩下s-k个直接连节点1的位置,如图:

对于每次操作,我们都找出1到每个点的路上最长的边,然后朴素找差值最大的边进行删除加入。

#include<bits/stdc++.h>
using namespace std;

const int N=25;
int n,te,tot,tk,ans;
int f[N],st[N],dw[N][N];
bool ext[N][N];

struct e_
{
	int u,v,w;
}e[N*N];
struct dis_
{
	int u,v,w;
}dis[N];
map<string,int>name;


bool cmp(e_ a,e_ b)
{
	return a.w<b.w;
}

int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}

void Kruskal()
{
	sort(e+1,e+te+1,cmp);
	for(int i=1;i<=te;++i)
	{
		int x=find(e[i].u),y=find(e[i].v),w=e[i].w;
		
		if(x==1||y==1||x==y) continue;
		
		x<y?f[x]=y:f[y]=x;
		ans+=w;
		ext[e[i].u][e[i].v]=ext[e[i].v][e[i].u]=1;
	}

	for(int i=2;i<=tot;++i)
	{
		int ff=find(i);

		if(dw[1][i]&&(!st[ff]||dw[1][i]<dw[1][st[ff]])) st[ff]=i;
		if(f[i]==i) tk++,ans+=dw[1][st[i]],ext[1][st[i]]=ext[st[i]][1]=1;
	}
}
void dfs(int u,int f)
{
	for(int v=2;v<=tot;++v)
	{
		if(v==f||!ext[u][v]) continue;
		
		if(dis[v].w==-1)
		{
			dis[v]=dis[u].w>dw[u][v]?dis[u]:(dis_){u,v,dw[u][v]};
			dfs(v,u);
		}
	}
}
void change(int k)
{
	while(k--)
	{
		memset(dis,-1,sizeof(dis));
		dfs(1,-1);
		int v,val=0;
		
		for(int i=2;i<=tot;++i)
		if(!ext[1][i]&&dw[1][i]&&dis[i].w-dw[1][i]>val)	v=i,val=dis[i].w-dw[1][i];
	
		if(val<=0) break;
		
		ans-=val;
		ext[dis[v].u][dis[v].v]=ext[dis[v].u][dis[v].v]=0;
		ext[1][v]=ext[v][1]=1;
	}
}

int main()
{
	scanf("%d",&n);
	name["Park"]=++tot;
	f[tot]=tot;
	for(int i=1;i<=n;++i)
	{
		int w;
		string s1,s2;
		
		cin>>s1>>s2;
	
		scanf("%d",&w);
		
		if(!name[s1]) name[s1]=++tot,f[tot]=tot;
		if(!name[s2]) name[s2]=++tot,f[tot]=tot;
								
		int u=name[s1],v=name[s2];
		
		e[++te]=(e_){u,v,w};
		e[++te]=(e_){v,u,w};
		dw[u][v]=dw[v][u]=w;
	}				
	
	Kruskal();

	int s;
	scanf("%d",&s);
	
	change(s-tk);
	
	printf("Total miles driven: %d\n",ans);
}

Prim算法O(mlogn)

与Dijkstra算法相似。

假设已经加入树的点属于集合T,未加入树的点的集合属于S,每次找到minx∈S,y∈T,即未加入树的点中,加入树距离最小的点,将该点加入树。

从已经加入树的节点中取点,扫描它的所有出边,更新别的端点。

void prim()
{
	memset(d,0x3f,sizeof(d));
	d[1]=0;
	for(int i=1;i<n;++i)
	{
		int u=0;
		for(int i=1;j<=n;++j)
		if(!vis[j]&&d[j]<d[u]) u=j;
		
		vis[u]=1;
		
		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v,w=e[i].w;
			if(!vis[v]&&d[v]>d[u]+w) d[v]=d[u]+w;
		}
	}
}

沙漠城堡

大卫大帝刚刚建立了一个沙漠帝国,为了赢得他的人民的尊重,他决定在全国各地建立渠道,为每个村庄提供水源。

与首都相连的村庄将得到水资源的浇灌。

他希望构建的渠道可以实现单位长度的平均成本降至最低。

换句话说,渠道的总成本和总长度的比值能够达到最小。

他只希望建立必要的渠道,为所有的村庄提供水资源,这意味着每个村庄都有且仅有一条路径连接至首都。

他的工程师对所有村庄的地理位置和高度都做了调查,发现所有渠道必须直接在两个村庄之间水平建造。

由于任意两个村庄的高度均不同,所以每个渠道都需要安装一个垂直的升降机,从而使得水能够上升或下降。

建设渠道的成本只跟升降机的高度有关,换句话说只和渠道连接的两个村庄的高度差有关。

需注意,所有村庄(包括首都)的高度都不同,不同渠道之间不能共享升降机。

输入格式
输入包含多组测试数据。

每组测试数据第一行包含整数N,表示村庄(包括首都)的总数目。

接下来N行,每行包含三个整数x,y,z,描述一个村庄的地理位置,(x,y)为该村庄的位置坐标,z为该村庄的地理高度。

第一个被描述的村庄即为首都。

当输入一行为0时,表示输入终止。

输出格式
每组数据输出一个结果,每个结果占一行。

结果为一个保留三位小数的实数,表示渠道的总成本和总长度的比值的最小值。

数据范围
2≤N≤1000,
0≤x,y<10000,
0≤z≤10000000
输入样例:

4
0 0 0
0 1 1
1 1 2
1 0 3
0

输出样例:

1.000

输出样例:
1.000

最小生成树,最小值乘积相除最小。

前置知识:01规划

输入样例 1

5 3
1 2 4 1 2
4 3 9 3 7

输出样例 1

0.4667

输入样例 2

3 2
5 0 2
5 1 6

输出样例 2

0.8333

输入样例 3

10 6
1 5 3 7 2 8 5 4 2 6
15 35 12 12 9 15 7 7 13 15

输出样例 3

0.4923

题解:

设答案为L,则有

\(∑ai*xi/∑bi*xi>=L\)
\(∑ai*xi-L*∑bi*xi>=0\)
\(∑(ai-L*bi)*xi>=0\)

则二分找L,将单开一个数组储存\((ai-L*bi)\),由于要找大于0的,如果存在一组解满足有k个\(xi=1\)并且和大于等于0,则数组中最大的k个数加起来一定大于等于0,所以直接取从大到小排序后最大的k个数。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;
int n, k;
double a[N], b[N], c[N];

bool cmp(double a, double b) { return a > b; }

bool judge(double ans) {
    for (int i = 1; i <= n; ++i) c[i] = a[i] - ans * b[i];

    sort(c + 1, c + n + 1, cmp);

    double res = 0;
    for (int i = 1; i <= k; ++i) res += c[i];

    return res >= 0;
}

int main() {
    scanf("%d %d", &n, &k);

    for (int i = 1; i <= n; ++i) scanf("%lf", &a[i]);
    for (int i = 1; i <= n; ++i) scanf("%lf", &b[i]);

    double l = 0, r = 1e5 + 5, rep = 0.000001;

    while (r - l >= rep) {
        double m = (l + r) / 2;

        if (judge(m))
            l = m + rep;
        else
            r = m - rep;
    }

    printf("%.4lf", l);
}

于是本题代码就出来了。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define sqr(n) ((n)*(n))
using namespace std;

const int maxn = 1e3 + 5;
const double eps = 1e-6;

struct node{ int x,y,z; };

int n, v[maxn];
vector<node> ve;
double ans, e[maxn][maxn], dis[maxn];

void prim(double x)
{
    rep (i, 1, n)
    {
        double mi = 2e9;
        int now;
        rep (j, 0, n - 1)
            if(!v[j] && dis[j] < mi) mi = dis[j], now = j;

        v[now] = 1;
        ans += dis[now];
        rep (j, 0, n - 1)
            dis[j] = min(dis[j], abs(ve[now].z - ve[j].z) - x * e[now][j]);
    }

}

bool judge(double x)
{
    rep (i, 0, n - 1) dis[i] = 2e9, v[i] = 0;
    dis[0] = 0; ans = 0;
    prim(x);
    return ans > 0;
}

int main()
{
    while (cin >> n, n)
    {
        vector<node>().swap(ve);

        for (int i = 1, x, y, z; i <= n; ++i)
        {
            scanf("%d %d %d",&x,&y,&z);
            ve.push_back(node{x, y, z});
        }                      

        rep (i, 0, n - 1)
            rep (j, 0, n - 1)
                e[i][j] = sqrt(0.0 + sqr(ve[i].x - ve[j].x) + sqr(ve[i].y - ve[j].y));

        double l = 0, r = 2e9;
        while(r - l > eps)
        {
            double mid = (l + r) / 2;
            if(judge(mid)) l = mid;
            else r = mid;
        }
        printf("%0.3f\n", r);
    }
    return 0;
}

黑暗城堡

在顺利攻破Lord lsp的防线之后,lqr一行人来到了Lord lsp的城堡下方。

Lord lsp黑化之后虽然拥有了强大的超能力,能够用意念力制造建筑物,但是智商水平却没怎么增加。

现在lqr已经搞清楚黑暗城堡有N个房间,M条可以制造的双向通道,以及每条通道的长度。

lqr深知Lord lsp的想法,为了避免每次都要琢磨两个房间之间的最短路径,Lord lsp一定会把城堡修建成树形的。

但是,为了尽量提高自己的移动效率,Lord lsp一定会使得城堡满足下面的条件:

设 D[i] 为如果所有的通道都被修建,第 i 号房间与第1号房间的最短路径长度;而 S[i] 为实际修建的树形城堡中第 i 号房间与第1号房间的路径长度;要求对于所有整数 i,有 S[i]=D[i] 成立。

为了打败Lord lsp,lqr想知道有多少种不同的城堡修建方案。

你需要输出答案对 231–1 取模之后的结果。

输入格式
第一行有两个整数 N 和 M。

之后 M 行,每行三个整数X,Y 和L,表示可以修建 X 和 Y 之间的一条长度为 L 的通道。

输出格式
一个整数,表示答案对 231–1 取模之后的结果。

数据范围
2≤N≤1000,
N−1≤M≤N(N−1)/2,
1≤L≤100
输入样例:
3 3
1 2 2
1 3 1
2 3 1
输出样例:
2

由题可知,即给出n个点m条边求最小生成树的组成方案有多少。

那么只需要在Prim的基础上增加一个sum数组,记录有几个点可以到v,最后把sum数组累乘就好了。

结合图示:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e3+5,p=(1<<31)-1;
ll ans=1,sum[N];
int n,m,d[N];
bool vis[N];
struct t_
{
	int v,w;
};
deque<t_>nxt[N];

void Prim()
{
	memset(d,0x3f,sizeof(d));
	d[1]=0;
	sum[1]=1;
	
	for(int k=1;k<n;++k)
	{
		int u=0;
		for(int i=1;i<=n;++i)
		if(!vis[i]&&d[i]<d[u]) u=i;
		
		vis[u]=1;
		
		for(int i=0;i<nxt[u].size();++i)
		{
			int v=nxt[u][i].v,w=nxt[u][i].w;
			if(!vis[v])
			{
				if(d[v]==d[u]+w) sum[v]++;
				else
				if(d[v]>d[u]+w) d[v]=d[u]+w,sum[v]=1;
			}
		}
	}
}

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1,u,v,w;i<=m;++i)
	{
		scanf("%d %d %d",&u,&v,&w);
		nxt[u].push_back((t_){v,w});
		nxt[v].push_back((t_){u,w});
	}
	
	Prim();
	
	for(int i=1;i<=n;++i)
	ans=(ans*sum[i])%p;
	
	printf("%d",ans);
}

最小生成树计数

#include<bits/stdc++.h>
using namespace std;
#define u(i) e[i].u
#define v(i) e[i].v
#define w(i) e[i].w
#define l(i) a[i].l
#define r(i) a[i].r
#define v(i) a[i].v
const int N=105,M=1e3+5,mod=31011;
int n,m,cnt,tot,ans=1,sum;
int fa[N];
struct E_
{
	int u,v,w;
	friend bool operator<(E_ a,E_ b)
	{
		return a.w<b.w;
	}
}e[M];
struct A_{int l,r,v;}a[M];
int find(int x)
{
	return fa[x]!=x?fa[x]=find(fa[x]):x;
}
void dfs(int x,int now,int k)
{
	if(now==r(x)+1)
	{
		if(k==v(x)) sum++;
		return;
	}
	int x=find(u(now)),y=find(v(now));
	if(x!=y)
	{
		fa[x]=y;
		dfs(x,now+1,k+1);
		fa[x]=x;fa[y]=y;
	}
	dfs(x,now+1,k);
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=m;++i) scanf("%d %d %d",&u(i),&v(i),&w(i));
	soer(e+1,e+m+1);
	for(int i=1;i<=m;++i)
	{
		if(w(i)!=w(i-1)){r(cnt)=i-1;l(++cnt)=i;}
		int x=find(u(i)),y=find(v(i));
		if(x!=y) {fa[x]=y;v(i)++;tot++;}
	}
	r(cnt)=m;
	if(tot!=n-1){printf("0");return 0;}
	
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=cnt;++i)
	{
		sum=0;
		dfs(i,l(i),0);
		ans=(ans*sum)%mod;
		for(int j=l(i);j<=r(i);++j)
		{
			int x=find(u(j)),y=find(y(i));
			if(x!=y) return fa[x]=y;
		}
	}
	printf("%d",ans);
	return 0;
}
posted @ 2020-10-23 19:49  林生。  阅读(141)  评论(0)    收藏  举报