k度限制最小生成树 POJ --- 1639

题目大意:
给出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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容