D3生成树专题
这一天不知道怎的上课 竟然我说了两道题正解;
第一题:我写过一篇较详细的博客:https://www.cnblogs.com/Tyouchie/p/10366967.html
第二题:UVA10369
改编后大致题意:南极有n个科研站,要用卫星或无线电把他们连起来,无线电的费用随着距离增加而增加,并且长传播距离为d,现在有s个卫星,任意两个安装了卫星的设备无论距离多远都可以直接通信,求一个方案使 得d最小。
s ≤ 1时求最小生成树即可。 s ≥ 2时,等于孤立了s − 1个区域,即s − 1条边置为0,当然是最小生成树中最大的s − 1条。 kruskal的过程中直接计算即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<algorithm> #include<cctype> #include<cmath> #include<complex> #include<cstdio> #include<cstring> #include<deque> #include<functional> #include<list> #include<map> #include<iomanip> #include<iostream> #include<queue> #include<set> #include<stack> #include<string> #include<vector> using namespace std; int nc,x[510],p,y[510],s; int father[510]; inline int read() { char c; int r; while(c=getchar()){if((c>='0')&&(c<='9')){r=c^0x30;break;}} while(isdigit(c=getchar())) r=(r<<3)+(r<<1)+(c^0x30); return r; } int find(int x) { if(father[x]!=x) father[x]=find(father[x]); return father[x]; } void Union(int x,int y) { int fx=find(x); int fy=find(y); if(fx!=fy) father[fx]=fy; } struct node { int x,y; double v; bool operator <(const node &b) const { return v<b.v; } }q[1100000]; int main() { nc=read(); while(nc--) { memset(x,0,sizeof(x)); memset(y,0,sizeof(y)); s=read(); p=read(); int i=1,j=1,k=0,len=0; double ans=0.00; while(i<=p) { x[i]=read(); y[i]=read(); ++i; } i=0; while(i<=p) { father[i]=i; ++i; } i=1; while(i<p) { j=i+1; while(j<=p) { q[++len]=(node){i,j,sqrt(double((x[i]-x[j])*(x[i]-x[j]))+double((y[i]-y[j])*(y[i]-y[j])))}; ++j; } ++i; } sort(q+1,q+len+1); i=1; while(i<=len) { if(find(father[q[i].x])!=find(father[q[i].y])) { Union(q[i].x,q[i].y); ans=q[i].v; ++k; } if(k==p-s) break; ++i; } printf("%.2lf\n",ans); } return 0; }
第三题:bzoj1232
n个点m条双向边,要求去掉一些边仅保留n − 1条边,每个点有各自的点权,每条边有各自的边权,要求从一个点出发遍历每一个点再回到起点,每经过一次点i,代价就加上一次该点点权,每经过一次边j, 代价就加上一次该边的边权。求最小的代价;
详解鉴我另一篇博客;https://www.cnblogs.com/Tyouchie/p/10366912.html
第四题:poj2784(poj上无多组数据),下面代码是多组数据;
改编后题意:平面上有n个点,你的任务是让所有n个点连通,为此,你可以新建一些边,费用等于两个端点的欧几里得距离的平方。另外还有q个套餐, 可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互连通,第i个套餐的花费为ci。求最小花费。 1 ≤ n ≤ 1000, 0 ≤ q ≤ 8。
枚举选择哪个套餐后再求最小生成树即可。q只有8,最多2的八次方枚举;这里用到二进制枚举;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int N=1010,M=(int)1e6+10,Q=10,X=3010,D=(int)2e6+10; typedef long long LL; inline int read() { double x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,p,m; int fa[N]; struct hp { int c,t; int s[N]; } b[Q]; struct vert { int x,y; } a[N]; struct edge { int x,y,d; } e[M]; LL mmin(LL x,LL y) { return x<y?x:y; } int W_dis(int i,int j) { return (a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y); } void add(int id,int x,int y,int d) { e[id].x=x,e[id].y=y,e[id].d=d; } bool cmp(edge x,edge y) { return x.d<y.d; } int ffind(int x) { if (fa[x]!=x) fa[x]=ffind(fa[x]); return fa[x]; } LL kruskal() { int i,t=0; int cnt=0; LL ans=0; sort(e+1,e+1+m,cmp); for (i=1; i<=n; i++) fa[i]=i; for (i=1; i<=m; i++) { int x=e[i].x,y=e[i].y; int xx=ffind(x),yy=ffind(y); if (xx!=yy) { fa[xx]=yy; cnt++,ans+=e[i].d; e[++t]=e[i]; if (cnt==n-1) break; } } m=t; return ans; } LL kruskal2(int cnt) { int i; LL ans=0; for (i=1; i<=m; i++) { int x=e[i].x,y=e[i].y; int xx=ffind(x),yy=ffind(y); if (xx!=yy) { fa[xx]=yy; cnt++,ans+=e[i].d; if (cnt==n-1) break; } } return ans; } int main() { int T,i,j,k; scanf("%d",&T); while (T--) { n=read();p=read(); for(i=1;i<=p;i++) { b[i].t=read();b[i].c=read(); for(j=1;j<=b[i].t;j++) b[i].s[j]=read(); } for(i=1;i<=n;i++) a[i].x=read(),a[i].y=read(); m=0; for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) add(++m,i,j,W_dis(i,j)); LL ans=kruskal(); for (i=0; i<(1<<p); i++) { for (j=1; j<=n; j++) fa[j]=j; int cnt=0,h=0; for (j=1; j<=p; j++) if (i&(1<<(j-1))) { h+=b[j].c; for (k=2;k<=b[j].t;k++) { int x=b[j].s[1],y=b[j].s[k]; int xx=ffind(x),yy=ffind(y); if (xx!=yy) fa[xx]=yy,cnt++; } } ans=mmin(ans,h+kruskal2(cnt)); } printf("%lld\n",ans); if (T) printf("\n"); } return 0; }
第五题:poj3522
题意:求最大边与最小边差值最小的生成树。 2 ≤ n ≤ 100, 1 ≤ m ≤ n(n−1) 2 。
最小生成树有一个很重要的性质:在构造生成树时有可能选择不同 的边,但最小生成树的权是唯一的。所以在用kruskal算法时第一次加入 的必然是最小生成树的最小边权值,最小边确定后,最小生成树的最大 边的权值是所以生成树中最小的,于是只要枚举最小边,然后求最小生 成树,就可以得到最大边,只要每次更新最优解就行了。 还有一个解法是用动态树link-cut-tree,可以再把复杂度降 成O(mlogn)。
这里只有小数据版;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cctype> #include<cstring> #include<algorithm> #include<queue> #include<vector> using namespace std; inline int read() { int num=0,f=1;char ch=getchar(); while (!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while (isdigit(ch)) num=(num<<3)+(num<<1)+(ch^48), ch=getchar(); return num*f; } int n,m,ans,father[51000],sum; struct pink { int x,y,v; }a[51000]; bool mycmp(pink a,pink b) { return a.v<b.v; } int find(int x) { if(x==father[x]) return x; else return father[x]=find(father[x]); } int main() { while(1) { n=read();m=read();int i=0; if((n==m)&&(m==0)) exit(0); if(n==1) { cout<<'0'<<endl; exit(0); } for(int i=1;i<=m;i++) a[i].x=read(),a[i].y=read(),a[i].v=read(); sort(a+1,a+m+1,mycmp); ans=a[m].v; for(int k=1;k<=m;k++) { sum=0; for(i=1;i<=n;i++) father[i]=i; for(i=k;i<=m;i++) { int x=find(a[i].x),y=find(a[i].y); if(x==y) continue; father[x]=y; ++sum; if(sum==n-1) { ans=min(ans,a[i].v-a[k].v); break; } } if(i==m+1) break; } if(ans==a[m].v) cout<<"-1"<<endl; else cout<<ans<<endl; } return 0; }
第六题:UVA10816
题意:一群人在沙漠中,给定了n个点,m条路,双向的。每条路有一定 的长度且路上的温度也不一样。现在这群人想从s到t去,要使路径中的 最高温度最低,有多条路径的情况下选择路程最短的,输出路径,最高 温度,路程。 1 ≤ n ≤ 100, 1 ≤ m ≤ 10000。
最小生成树+最短路;
如果只考虑最小的最大热度,那么本题就是一个最小瓶颈路问题, 只需按照热度找一棵最小生成树即可。但是,如果这样的路径有多个,实际上是最小生成树有多个时,要找到最短路径,还得把热度不大于最小生成树中最大热度的边并且没在生成树中的边加到最小生成树中,然 后再找最短路。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <algorithm> #include <cctype> #include <cmath> #include <complex> #include <cstdio> #include <cstring> #include <deque> #include <functional> #include <list> #include <map> #include <iomanip> #include <iostream> #include <queue> #include <set> #include <stack> #include <string> #include <vector> using namespace std; typedef long long ull; const int maxn = 1e4 + 100; inline int read() { int s = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); } return s * w; } int path[maxn], f[maxn], head[maxn], vis[maxn], P[maxn], g[maxn][maxn]; double dis[maxn], d; struct Edge { int next, to; double dis; }edge[maxn]; int num_edge = 0, psize = 0, cnt = 0, flag = 0; int n, e, st, ed; double maxtemp = -1; struct Node { int from, to; double dis, temp; }t[maxn]; const double dinf = 9999.99; const double eps = 1e-8; queue<int> q; inline bool mycmp(Node A, Node B) { return A.temp < B.temp; } inline int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } inline void add(int from, int to, double dis) { edge[++num_edge].to = to; edge[num_edge].dis = dis; edge[num_edge].next = head[from]; head[from] = num_edge; } void spfa(int k) { for (int i = 1; i <= n; ++i) { dis[i] = dinf; vis[i] = 0; } dis[k] = 0, vis[k] = 1; q.push(k); while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for (int i = head[u]; i; i = edge[i].next) { int v = edge[i].to; if (dis[v] - eps > dis[u] + edge[i].dis) { path[v] = u; dis[v] = dis[u] + edge[i].dis; if (!vis[v]) { q.push(v); vis[v] = 1; } } } } } inline void clean() { num_edge = 0; psize = 0; cnt = 0; d = 0; flag = 0; memset(P, 0, sizeof(P)); while (!q.empty()) q.pop(); memset(head, 0, sizeof(head)); memset(path, 0, sizeof(path)); memset(edge, 0, sizeof(edge)); memset(t, 0, sizeof(t)); memset(f, 0, sizeof(f)); } int main() { while (scanf("%d %d %d %d", &n, &e, &st, &ed) == 4) { clean(); for (int i = 1; i <= e; ++i) { f[i] = i; t[i].from = read(); t[i].to = read(); scanf("%lf %lf", &t[i].temp, &t[i].dis); } for (int i = 1; i <= n; ++i) f[i] = i; sort(t + 1, t + e + 1, mycmp); for (int i = 1; i <= e; ++i) { int u = find(t[i].from), v = find(t[i].to); if (u != v) f[u] = v; if (find(st) == find(ed)) { maxtemp = t[i].temp; flag = 1; break; } } for (int i = 1; i <= e; ++i) { if (t[i].temp - eps< maxtemp) { add(t[i].from, t[i].to, t[i].dis); add(t[i].to, t[i].from, t[i].dis); } } spfa(st); if (!flag) continue; P[++psize] = ed; for (int i = path[ed]; i; i = path[i]) P[++psize] = i; for (int i = psize; i > 1; --i) printf("%d ", P[i]);cout<<P[1]; printf("\n"); printf("%.1lf %.1lf\n", dis[ed], maxtemp); } return 0; }
第七题:poj1639
n个点m条边的图,求它的最小生成树满足点v0的度最大为k。
参考材料:国家集训队2004论文集汪汀 https://wenku.baidu.com/view/8abefb175f0e7cd1842536aa.html