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

链接

题目大意: 
给出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
#include
#include
#include
#include
#include
#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.wint 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//初始化根节点.
        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();
}

你可能感兴趣的:(K度限制生成树)