POJ --- 1639 【k度限制最小生成树】
2017-06-21 11:43
405 查看
链接
题目大意:
给出m条边,每条边有两个端点和一个权值;
求这个图在满足以下条件的情况下的最小生成树;
在所有点中,有一个特殊点Park,它在求得的最小生成树中的度必须小于等于某个值;
思路:
k度限制最小生成树,也就是某个顶点有度数限制的生成树。
对这道题来说是名字为Park的顶点有度数限制,最大为k,我们把这个顶点设为v0好了。
首先需要知道生成树有一个回路性质:设C为图G中的一个回路,边e是C上权值最大的边,
则图G的所有生成树均不包含e。
然后我们能给出求度限制最小生成树的几个步骤:
(1)先在图G中删掉顶点v0,然后求剩下的图的最小生成树,得到图G1,此时可能会有若干个生成树,
它们分别都是图中的一个连通分量。
(2)然后让v0与这些连通分量都分别连上一条边,该边为图G中v0与某个连通分量中的所有顶点
的边中最短的那条,得到图G2,此时可以得到图G中出现生成树时v0的最小度数ki。
(3)目前我们已经得到v0度数等于ki时的最小生成树,接下来要得到v0度数小于等于k的最小生成树,只要能找到求出v0度数等于ki+1的最小生成树的方法就可以了。这个方法需要用到上面的回路性质,首先因为G2是一棵树,所以如果我们在图G2中给v0和其他顶点连上一条边(即增加了v0的度数),则一定会出现一条回路,根据回路性质我们需要找到这条回路中权值最大的边(除了与v0相连的边),并把它删去,这样就得到了ki+1的最小生成树。
(麻烦在树上的点居然都是名字没有具体的数字代表,否则就好处理多了,所以需要根据名字来建图)
题目大意:
给出m条边,每条边有两个端点和一个权值;
求这个图在满足以下条件的情况下的最小生成树;
在所有点中,有一个特殊点Park,它在求得的最小生成树中的度必须小于等于某个值;
思路:
k度限制最小生成树,也就是某个顶点有度数限制的生成树。
对这道题来说是名字为Park的顶点有度数限制,最大为k,我们把这个顶点设为v0好了。
首先需要知道生成树有一个回路性质:设C为图G中的一个回路,边e是C上权值最大的边,
则图G的所有生成树均不包含e。
然后我们能给出求度限制最小生成树的几个步骤:
(1)先在图G中删掉顶点v0,然后求剩下的图的最小生成树,得到图G1,此时可能会有若干个生成树,
它们分别都是图中的一个连通分量。
(2)然后让v0与这些连通分量都分别连上一条边,该边为图G中v0与某个连通分量中的所有顶点
的边中最短的那条,得到图G2,此时可以得到图G中出现生成树时v0的最小度数ki。
(3)目前我们已经得到v0度数等于ki时的最小生成树,接下来要得到v0度数小于等于k的最小生成树,只要能找到求出v0度数等于ki+1的最小生成树的方法就可以了。这个方法需要用到上面的回路性质,首先因为G2是一棵树,所以如果我们在图G2中给v0和其他顶点连上一条边(即增加了v0的度数),则一定会出现一条回路,根据回路性质我们需要找到这条回路中权值最大的边(除了与v0相连的边),并把它删去,这样就得到了ki+1的最小生成树。
算法不难理解,实现起来会有些复杂,比较重要的一个优化是找到回路中权值最大的边时先用dp求出并保存v0到其他顶点的路径中权值最大的边,这样就只不用每次都遍历整个图了.
AC代码:(麻烦在树上的点居然都是名字没有具体的数字代表,否则就好处理多了,所以需要根据名字来建图)
#include<cstdio> #include<iostream> #include<cstring> #include<map> #include<vector> #include<algorithm> #define CLR(x) memset(x,0,sizeof(x)) using namespace std; const int maxn = 105; const int inf = 1e9; int n,m; // n表示边的数量,m表示限度值. int cnt; //计算出来的节点值. int pre[maxn]; bool flag[maxn][maxn]; //标记那些点之间是连通关系的. int G[maxn][maxn]; //存入的初始化信息. int ans; map<string,int> Map; struct node{ int u,v,w; } a[maxn*maxn]; node dp[maxn*maxn]; //dp[v]为路径v0—v上与v0无关联且权值最大的边 bool cmp(node a,node b){ return a.w<b.w; } int Find(int x){ return pre[x]==x?x:pre[x]=Find(pre[x]); } int get_sum(string s) //返回每个人对应的节点. { if(Map.find(s)==Map.end()) // 没有搜索到该键值,这里用了map的一个操作函数find. { Map[s]=++cnt; } return Map[s]; } void init() { ans=0; cnt=1; Map["Park"]=1; //把那个特殊的点设置为1. CLR(flag); //初始化. memset(G,-1,sizeof(G)); //初始化. scanf("%d",&n); for(int i=1;i<maxn;i++) //初始化根节点. pre[i]=i; string s; for(int i=1;i<=n;i++){ //对应建图. cin >> s; a[i].u=get_sum(s); cin >> s; a[i].v=get_sum(s); cin >> a[i].w; if(G[a[i].u][a[i].v] == -1) G[a[i].u][a[i].v]=G[a[i].v][a[i].u]=a[i].w; //G中存在是建的那个对应点的边. else //有重边的情况. G[a[i].u][a[i].v]=G[a[i].v][a[i].u]=min(G[a[i].v][a[i].u],a[i].w); } scanf("%d",&m); } void kruskal() { for(int i=1;i<=n;i++){ if(a[i].u==1 || a[i].v==1 ) continue; int u=Find(a[i].u); int v=Find(a[i].v); //printf("%d %d %d\n",a[i],u,a[i].v,a[i].w); //cout << u << " " << v << endl; if( u != v){ // Un(u,v); pre[v]=u; flag[a[i].u][a[i].v]=flag[a[i].v][a[i].u]=true; ans+=a[i].w; } } /*for(int i=1;i<=cnt;i++){ printf("%d ",pre[i]); } printf("\n");*/ } void dfs(int x,int fa) //更新dp过程.(也就是加的优化,但也是最难懂的地方.b ) { for(int i=2;i<=cnt;i++){ if(i==fa) continue; if(flag[x][i]) { //printf("%d %d\n",i,x); if(dp[i].w != -1){ //v0和那些点不想连. if(dp[x].w < G[x][i]) //dp(v)=max(dp(father(v)),w(father(v),v)); { dp[i].w=G[x][i]; dp[i].u=x; dp[i].v=i; //printf("%d %d %d \n",dp[i].w,dp[i].u,dp[i].v); } else dp[i]=dp[x]; } //printf("%d %d\n",i,x); dfs(i,x); } } } void solve() { int tmp[maxn],minn[maxn]; for(int i=1;i<=cnt;i++) minn[i]=inf; sort(a+1,a+1+n,cmp); /*for(int i=1;i<=n;i++){ printf("%d %d %d\n",a[i].u,a[i].v,a[i].w); }*/ kruskal(); //cout << ans << endl; //minn[t]表示顶点1到连通分量t的最小边的权,tmp[t]表示连通分量t中与到顶点1的最小边相连的顶点. for(int i=2;i<=cnt;i++){ if(G[1][i]!=-1){ //该点和那些点相连. int t=Find(i); //找出根节点. 因为 t 不一定等于 i ; if(minn[t]>G[1][i]) //求每个连通分量中和顶点1连接的最小权边 { //这道题中就只有一个联通分量,所以minn中其实也只存有一个元素. tmp[t] = i; //保存这个连通分量中直接与该点相连的点是哪个点. minn[t] = G[1][i]; } } } /*for(int i=1;i<=cnt;i++) printf("%d ",minn[i]); printf("\n");*/ int t=0; //t表示最小限度. for(int i=1;i<=cnt;i++){ if(minn[i]!=inf){ t++; flag[1][tmp[i]]=flag[tmp[i]][1]=true; //并把顶点1与连通分量连接起来.这样整个图就形成了一颗最小生成树了. ans += G[1][tmp[i]]; } } //得到最小m度生成树. //cout << t << endl; //有几个连通分量,就有t就等于多少.题目保证必有答案,所以t一定是<=m的.就算刚好等于,也会输出一个答案ans,即上面那个处理过了的ans. for(int i=t+1;i<=m;i++){ //枚举t到m的所有最小生成树,即一步步将v1点的度加1,直到v1点的度为m为止. //dp[v]为路径v0—v上与v0无关联且权值最大的边. dp[1].w = -1; for(int j=2;j<=cnt;j++){ if(flag[1][j]) dp[j].w = -1; else dp[j].w = 0 ; } dfs(1,-1); /*for(int i=1;i<=cnt;i++){ printf("%d %d %d\n",dp[i].u,dp[i].v,dp[i].w); } printf("\n\n\n");*/ //dp[3]等于-inf是因为前面要在联通分量中选择一条最短的路劲与v0直接相连,所以他是-inf. int tmp,Min=inf; for(int j=2;j<=cnt;j++){ if(G[1][j]==-1) continue; if(Min > G[1][j]-dp[j].w){ Min = G[1][j]-dp[j].w; tmp = j; } } if(Min>=0) //找不到这样的边,就说明已经求出 break; flag[1][tmp]=flag[tmp][1]=true; int u=dp[tmp].u; int v=dp[tmp].v; flag[u][v]=flag[v][u]=false; ans+=Min; } printf("Total miles driven: %d\n",ans); } int main() { init(); solve(); }
相关文章推荐
- poj 1639 有限制的最小生成树
- poj 1639 最小度限制生成树
- POJ - 1639 最小限制生成树
- POJ 1639 Picnic Planning 【最小度限制生成树】
- POJ-1639 Picnic Planning (最小度限制生成树)(模板题)
- POJ1639 Picnic Planning —— 根节点度数有限制的最小生成树
- poj1639 Picnic Planning 最小度数限制生成树
- poj 1639 Picnic Planning 单度限制的最小生成树
- POJ 1639 Picnic Planning(度限制最小生成树)
- poj1639 Picnic Planning 最小度数限制生成树
- POJ 1639 度限制最小生成树Prim
- 【POJ】1639 Picnic Planning 度限制最小生成树
- POJ 1639 Picnic Planning【度限制最小生成树】
- POJ 1639 Picnic Planning(初遇最小度限制生成树)
- [最小k度限制生成树] POJ 1639 Picnic Planning
- poj_1639 Picnic Planning(度限制最小生成树)
- 度限制最小生成树 POJ 1639 贪心+DFS+prim
- POJ 1639 有度限制的最小生成树
- POJ 1639 最小度限制生成树
- POJ 1639 Picnic Planning(有限制的最小生成树)