咳咳...
我是看了b站的up主 讲编程的王西西 而总结的他的视频。当然也有我自己的题目的思考。
首先,先贴一下最小生成树的模板。
题目形如:给定n个点,现在有m个边,每个边耗费一定体力值,请问从起点走到终点最少耗费多少体力值?
prime:稠密图
Kruskal 算法:(通用性更强 )代码很好写,涉及到并查集:过程:
初始状态一条边都不连,每个点属于自己的集合,按照边的权值从小到大排个序,然后按小到大遍历边,看边的起点和终点在不在同一个集合。如果不在就把他们连起来。(写模板的时候别忘了加个cnt)(代码省略部分过程,只是自己写的模板,仅供回忆思路)
struct EDGE{
int u,v,l;
}edge[M];
bool sorrt(struct EDGE i,struct EDGE j){
return i.l<j.l;
}
void find(int i){
if(fa[i]==i){
return i;
}
fa[i]=find(fa[i]);
return fa[i];
}
void unionn(int i,int j){
int fa_i=find(i);
int fa_j=find(j);
fa_i=fa[fa_j];
}
int main()
{
int ans=0,cnt++;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>edge[i].u>>edge[i].v>>edge[i].l;
}
sort(edge+1,edge+m+1,sorrt);
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
int fa_u=edge[i].u,fa_v=edge[i].v;
if(fa_u!=fa_v){
fa[fa_u]=fa[v];
ans+=edge[i].l;
cnt++;//若cnt==n-1,break即可;否则不是最小生成树;
}
}
cout<<ans;
return 0;
}
最小生成树用到的知识点:并查集,图的存储,排序;
现在看看最小生成树的子题:
1.题目如下
给你云朵的个数 N,再给你 M 个关系,表示哪些云朵可以连在一起。现在小杉要把所有云朵连成 K 个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。
输入格式
第一行有三个数 N,M,K。接下来 M 行每行三个数 X,Y,L表示 X 云和 Y 云可以通过 L 的代价连在一起。
输出格式
对每组数据输出一行,仅有一个整数,表示最小的代价。
如果怎么连都连不出 K 个棉花糖,请输出 No Answer。
输入输出样例
输入 #1复制
3 1 2
1 2 1
输出 #1复制
1
说明/提示
对于 30% 的数据,1≤N≤100,1≤M≤10 ^3
对于 100%100% 的数据,1≤N≤10^3
,1≤M≤10 ^4
,1≤K≤10,1≤X,Y≤N,0≤L<10^4
翻译:
本题实质上意思是,如何控制集合的个数,使得最后一共连成k个集合?那么就抓住cnt的意义,本来一共有n个集合,每加入一个点入集合,cnt次后集合还有n-cnt个,则令n-cnt=k即可。
2.题目如下:
题目描述:
又到了一年一度的明明生日了,明明想要买B样东西,巧的是,这B样东西价格都是A元。但是,商店老板说最近有促销活动,也就是:如果你买了第I样东西,再买第J样,那么就可以只花K[I,J]元,更巧的是,K[I,J]竟然等于K[J,I]。现在明明想知道,他最少要花多少钱。
输入输出格式:
输入格式:
第一行两个整数,A,B。接下来B行,每行B个数,第I行第J个为K[I,J]。我们保证K[I,J]=K[J,I]并且K[I,I]=0。特别的,如果K[I,J]=0,那么表示这两样东西之间不会导致优惠。
输出格式:
仅一行一个整数,为最小要花的钱数。
翻译:
首先这道题要学会把题目上的“图”翻译为树。而且两点是相互连通的(题目上说kij=kji)。其实不少题目涉及两点间相互关系的都可以试着往树上引导。不过这题还要再虚构一个0点,Kruskal即可。
3.题目:https://www.luogu.org/problemnew/show/P3063
题目背景
奶牛爱干草
题目描述
Bessie 计划调查N (2 <= N <= 2,000)个农场的干草情况,它从1号农场出发。农场之间总共有M (1 <= M <= 10,000)条双向道路,所有道路的总长度不超过1,000,000,000。有些农场之间存在着多条道路,所有的农场之间都是连通的。
Bessie希望计算出该图中最小生成树中的最长边的长度。
翻译:
做法1:二分去做,看所有边长小于mid是否成立,不成立就l=mid+1;
做法2:但其实,这道题目的答案就等价于求最小生成树的最长边。因为每次二分都要扫一下这个图,当集合变为1时break看mid是否成立;还不如就扫一遍这个图,当集合为1时停止,看最长边;
在此贴一个求s与t点间“最长生成树”的最短边的代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+7;
int n,m,k,s,t;
struct EDGE{
int u,v,l;
}edge[N];
int fa[N];
int find(int i){
if(fa[i]==i){
return i;
}
fa[i]=find(fa[i]);
return fa[i];
}
bool check(struct EDGE i,struct EDGE j){
return i.l>j.l;
}
int main()
{
cin>>n>>m>>k>>s>>t;
int z;
for(int i=1;i<=m;i++){
cin>>edge[i].u>>edge[i].v>>z;
if(z==-1){
edge[i].l=N;
}else{
edge[i].l=z;
}
}
sort(edge+1,edge+m+1,check);
int cnt=0,ans=N;
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
cnt++;
int fa_u=find(edge[i].u),fa_v=find(edge[i].v);
if(fa_u!=fa_v){
fa[fa_u]=fa_v;
}
if(find(s)==find(t)){
ans=min(k,edge[i].l);
break;
}
}
cout<<ans;
return 0;
}
4.题目描述:
农民John 决定将水引入到他的n(1<=n<=300)个牧场。他准备通过挖若
干井,并在各块田中修筑水道来连通各块田地以供水。在第i 号田中挖一口井需要花费W_i(1<=W_i<=100,000)元。连接i 号田与j 号田需要P_ij (1 <= P_ij <= 100,000 , P_ji=P_ij)元。
请求出农民John 需要为连通整个牧场的每一块田地所需要的钱数。
突破点:
虚构另一个点(可以是a[0])来存自生件水#的情况。之前再最小生成树专题里讲解过类似的题目。
在此处再贴一下那个题目的代码吧,可以着重看一下虚构点的操作。
#include<iostream>
#include<algorithm>
using namespace std;
int n,ans,id;
struct NODE{
int x,y,c;
}node[100010];
struct EDGE{
int u,v,l;
}edge[1000100];
int c[1000010],k[1000010],w;
int fa[100010];
//并查集的算法
int find(int i){
if(fa[i]==i){
return fa[i];
}
fa[i]=find(fa[i]);
return fa[i];
}
void unionn(int i,int j){
int fa_i=find(i);
int fa_j=find(j);
fa[fa_i]=fa_j;
}
//排序 (从小到大)
bool check(struct EDGE i,struct EDGE j){
return i.l<j.l;
}
int main()
{
cin>>id;
cin>>n;
for(int i=1;i<=n;i++){
cin>>node[i].x>>node[i].y;
}
for(int i=1;i<=n;i++){
cin>>c[i];
edge[w++]=EDGE{0,i,c[i]};
}
for(int i=1;i<=n;i++){
cin>>k[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
int u=1ll*(k[i]+k[j])*(abs(node[i].x-node[j].x)+abs(node[i].y-node[j].y));
edge[w++]=EDGE{i,j,u};
}
}
sort(edge,edge+w,check);
for(int i=0;i<w;i++){
cout<<edge[i].l<<endl;
}
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=0;i<w;i++){
int fa_u=find(edge[i].u),fa_v=find(edge[i].v);
if(fa_u!=fa_v){
unionn(edge[i].u,edge[i].v);
ans+=edge[i].l;
}
}
cout<<ans;
return 0;
}
5.题目描述:
先贴个图吧,(累)下次写
。。。
此贴供我复习treee使用...
6. 8.11日,今晚,很亮的月光...
我又回来了!把我今天整的题目贴在这里哦
链接:https://blog.youkuaiyun.com/2301_81888203/article/details/141110355
之前写的最小生成树很多都是给了3000个边(左右),但如果只给了点,且点的数量较多,就不能把所有边都列出来了,这样会超时。
先来看一道题目叭
最小代价
时间限制: 1.000 Sec 内存限制: 128 MB
提交状态
题目描述
给定 N 个二维平面上的点(xi,yi),将两个点(a,b),(c,d) 连接起来的代价是min(|a-c|,|b-d|) 。求最少需要多少的代价可以使整幅图联通,即对于任意的两个点之间可以直接或间接地到达。
输入
第一行包括一个整数N。表示点的个数。
接下来的N行,每行两个整数(xi,yi),表示第i个点的坐标。
输出
输出一个整数,表示需要的最少代价。
样例输入 Copy
3
1 5
3 9
7 8
样例输出 Copy
3
这道题有10^5个点,如果每个边都算,就有将近10^9条边,毫无疑问不行。那么我们就要注意到,有些点之间是不可能有边的(当两点距离大),那么只要把点与其最附近的点连起边来就好。那么怎么找其附近的点呢?注意这条题目的距离计算方式,横纵坐标差决定距离,那么自然就是把横纵坐标排一下顺序,然后“一个挨着一个”的记录入边。
下面就看一下代码就理解啦:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
int n;
struct NODE{
ll s;
int id;
}nodex[100010],nodey[100010];
struct EDGE{
int u,v;
ll l;
}edge[100010];
bool sorrt(struct NODE i,struct NODE j){
return i.s<j.s;
}
int fa[100010];
void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
int find(int i){
if(fa[i]==i){
return i;
}
fa[i]=find(fa[i]);
return fa[i];
}
bool sorrt1(struct EDGE i,struct EDGE j){
return i.l<j.l;
}
int t=0;
int main()
{
cin>>n;
init(n);
for(int i=1;i<=n;i++){
cin>>nodex[i].s>>nodey[i].s;
nodex[i].id=nodey[i].id=i;
}
sort(nodex+1,nodex+n+1,sorrt);
sort(nodey+1,nodey+n+1,sorrt);
for(int i=2;i<=n;i++){
edge[++t].u=nodex[i-1].id;
edge[t].v=nodex[i].id;
edge[t].l=nodex[i].s-nodex[i-1].s;
}
for(int i=2;i<=n;i++){
edge[++t].u=nodey[i-1].id;
edge[t].v=nodey[i].id;
edge[t].l=nodey[i].s-nodey[i-1].s;
}
sort(edge+1,edge+1+t,sorrt1);
ll cnt=0,ans=0;
for(int i=1;i<=t;i++){
int fa_u=find(edge[i].u),fa_v=find(edge[i].v);
if(fa_u!=fa_v){
fa[fa_u]=fa_v;
cnt++;
ans+=edge[i].l;
}
if(cnt==n-1){
break;
}
}
cout<<ans;
return 0;
}
其实这题就是将横纵坐标的数组排序,就可以筛选边了。
24.9.12 晚 更新一题
走廊泼水节
题目描述
【简化版题意】给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。求增加的边的权值总和最小是多少。
我们一共有N个OIER打算参加这个泼水节,同时很凑巧的是正好有N个水龙头(至于为什么,我不解释)。N个水龙头之间正好有N-1条小道,并且每个水龙头都可以经过小道到达其他水龙头(这是一棵树,你应该懂的..)。但是OIER门为了迎接中中的挑战,决定修建一些个道路(至于怎么修,秘密~),使得每个水龙头到每个水龙头之间都有一条直接的道路连接(也就是构成一个完全图呗~)。但是OIER门很懒得,并且记性也不好,他们只会去走那N-1条小道,并且希望所有水龙头之间修建的道路,都要大于两个水龙头之前连接的所有小道(小道当然要是最短的了)。所以神COW们,帮那些OIER们计算一下吧,修建的那些道路总长度最短是多少,毕竟修建道路是要破费的~~
输入
多组数据~
第一行t,表示有t组测试数据
对于每组数据
第一行N,表示水龙头的个数(当然也是OIER的个数);
2到N行,每行三个整数X,Y,Z;表示水龙头X和水龙头Y有一条长度为Z的小道
输出
对于每组数据,输出一个整数,表示修建的所有道路总长度的最短值。
样例输入 Copy
2 3 1 2 2 1 3 3 4 1 2 3 2 3 4 3 4 5
样例输出 Copy
4 17
提示
第一组数据,在2和3之间修建一条长度为4的道路,是这棵树变成一个完全图,且原来的树依然是这个图的唯一最小生成树.
每个测试点最多10组测试数据
50%n<=1500;
100%n<=6000
100%z<=100
这题其实也是最小生成树与最短路结合的变形题。如何保证修建完所有道路后,最小生成树仍然保持不变呢?我们可以模拟求最小生成树的过程:将所有边排序后,按序连接两个未连接的子集。注意:这时两个子集里的点是完全没有两个点相互连接的。所以要生成完全图,我们只需按这两个子区间连接所要的最短边连接所有边即可。假设子集u的元素个数为n1,子集v的元素个数为n2,则将这两个子集里面的点全部连接的边个数为n1*n2。
下面看一下代码吧。。。感觉我语言表述好差aaa -_-
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
int t;
int n;
struct EDGE{
int u,v,l;
}edge[100010];
bool sorrt(struct EDGE i,struct EDGE j){
return i.l<j.l;
}
int fa[1000010],sum[1000010];
void init(int n){
for(int i=1;i<=n;i++){
sum[i]=1;
fa[i]=i;
}
}
int find(int i){
if(fa[i]==i){
return i;
}
fa[i]=find(fa[i]);
return fa[i];
}
int solve(int n){
for(int i=1;i<=n-1;i++){
cin>>edge[i].u>>edge[i].v>>edge[i].l;
}
sort(edge+1,edge+n,sorrt);
init(n);
int ans=0;
for(int i=1;i<=n-1;i++){
int fa_u=find(edge[i].u),fa_v=find(edge[i].v);
if(fa_u!=fa_v){
ans+=(sum[fa_u]*sum[fa_v]-1)*(edge[i].l+1);
sum[fa_u]+=sum[fa_v];
fa[fa_v]=fa_u;
}
}
return ans;
}
signed main()
{
cin>>t;
while(t--){
cin>>n;
cout<<solve(n)<<endl;
}
return 0;
}