• 最小k度限制生成树


    【题目描述】

    给你一个图,n个点,m条边,求一颗生成树满足如下条件:

    (1)结点1的度不超过k。

    (2)在(1)条件下所求生成树最小。

    【算法引入】

    最小k度限制生成树,就是指有特殊的某一点的度不能超过k时的最小生成树。

    如果T是G的一个生成树且dT(v0)=k,则称T为G的k度限制生成树。

    G中权值和最小的k度限制生成树称为G的最小k度生成树。

    【算法思想】

    设特殊的那点为v0,先把v0删除,求出剩下连通图的所有最小生成树。

    假如有m棵最小生成树,那么这些生成树必定要跟v0点相连。

    也就是说这棵生成树的v0点至少是m度的。

    若m>k,条件不成立,无法找到最小k度限制生成树。

    若m<=k,则枚举m到k的所有最小生成树,即一步步将v0点的度加1,直到v0点的度为k为止。

    则v0点度从m到k的(k-m+1)棵最小生成树中最小的那棵即为答案。

    【算法步骤】

    (1) 原图中去掉和V0相连的所有边(可以先存两个图,建议一个邻接矩阵,一个边表,用方便枚举边的邻接表来构造新图)。

    得到m个连通分量,则这m个连通分量必须通过v0来连接。

    则在图G的所有生成树中dT(v0)>=m。

    则当k<m时,问题无解。

    对每个连通分量求一次最小生成树。

    每个连通分量的的最小生成树可以直接用一个循环,循环着Kruskal求出。

    这里利用了联通分量间的独立性,对每个连通分量分别求最小生成树,和放在一起求,毫不影响。

    而且kruskral算法保证了各连通分量边的有序性。

     1 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}//路径压缩
     2 bool cmp(node a,node b)  {return a.v<b.v;}
     3 void Kruskal()
     4 {
     5     for(int i=1;i<=n;i++)  f[i]=i;//并查集初始化
     6     sort(e+1,e+m+1,cmp);//按边权排序
     7     for(int i=1;i<=m;i++)
     8     {
     9         if(e[i].x==1||e[i].y==1)  continue;
    10         int x=find(e[i].x),y=find(e[i].y);
    11         if(x!=y)
    12         {
    13             f[x]=y;
    14             check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;//check表示有边相连
    15             ans+=e[i].v;
    16         }
    17     }
    18 }

    (2) 对于每个连通分量V’,用一条与V0直接连接的最小的边把它与V0点连接起来,使其整体成为一个生成树。

    就得到了一个m度限制生成树,即为最小m度限制生成树。

     1 void solve1()//计算最小m度生成树
     2 {
     3     for(int i=1;i<=n;i++)  Min[i]=INF;
     4     for(int i=2;i<=n;i++)//找出从1点连到每个连通块的最小边权
     5         if(a[1][i]!=-1)
     6         {
     7             int t=find(i);
     8             if(a[i][1]<Min[t])
     9             {
    10                 Min[t]=a[i][1];
    11                 temp[t]=i;
    12             }
    13         }
    14     for(int i=1;i<=n;i++)//把1点和每个连通块连接起来,计算答案
    15         if(Min[i]!=INF)
    16         {
    17             md++;
    18             check[1][temp[i]]=check[temp[i]][1]=1;
    19             ans+=a[1][temp[i]];
    20         }
    21 }

    (3)由最小m度限制生成树得到最小m+1度限制生成树。

    连接和V0相邻的点v,则可以知道一定会有一个环出现(因为原来是一个生成树);

    只要找到这个环上的最大权边(不能与v0点直接相连)并删除,就可以得到一个m+1度限制生成树;

    枚举所有和V0相邻点v,找到替换后,增加权值最小的一次替换(如果找不到这样的边,就说明已经求出);

    就可以求得m+1度限制生成树;

    如果每添加一条边,都需要对环上的边一一枚举,时间复杂度将比较高;

    用动态规划解决;

    设dp(v)为路径v0—v上与v0无关联且权值最大的边;

    定义father(v)为v的父结点,由此可以得到状态转移方程:

    dp(v)=max(dp(father(v)),ω(father(v),v));

    边界条件为dp[v0]=-∞(因为每次寻找的是最大边,所以-∞不会被考虑),dp[v’]=-∞|(v0,v’)∈E(T);

    当dT(v0)=k时停止(即当V0的度为k的时候停止),但不一定k的时候最优;

     1 void dfs(int x,int fa)//动规计算dp,dp记录的是从1到某点路径中权值最大的边,且此边不与1相连
     2 {
     3     for(int i=2;i<=n;i++)//枚举点
     4         if(check[x][i]&&i!=fa)//有边相连
     5         {
     6             if(dp[i].v==-1)//没被搜过,更新dp
     7             {
     8                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
     9                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
    10             }
    11             dfs(i,x);
    12         }
    13 }
    14 void solve2()//计算最小k度生成树
    15 {
    16     for(int i=md+1;i<=k;i++)
    17     {
    18         memset(dp,-1,sizeof(dp));  dp[1].v=-INF;
    19         for(int j=2;j<=n;j++)  if(check[1][j])  e[j].v=-INF;//把与1相连的边设为-oo,避免dfs时搜到
    20         dfs(1,0);  int t=0,minn=INF;
    21         for(int j=2;j<=n;j++)
    22             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)//记录对答案贡献最大的边
    23             {
    24                 minn=a[1][j]-dp[j].v;
    25                 t=j;
    26             }
    27         if(minn>=0)  break;//找不到就说明已经增广完了
    28         int x=dp[t].x,y=dp[t].y;
    29         check[1][t]=check[t][1]=1;//维护图的性质
    30         check[x][y]=check[y][x]=0;
    31         ans+=minn;
    32     }
    33 }

    【算法实现】

    完整代码:

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<cstdlib>
      5 #include<cmath>
      6 #include<ctime>
      7 #include<algorithm>
      8 using namespace std;
      9 #define INF 1000000000
     10 #define MAXN 105
     11 struct node{int x,y,v;}e[MAXN],dp[MAXN];//e是图的边表
     12 int n,m,k,ans,md,f[MAXN],Min[MAXN],temp[MAXN],a[MAXN][MAXN],check[MAXN][MAXN];
     13 inline int read()
     14 {
     15     int x=0,f=1;  char ch=getchar();
     16     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
     17     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
     18     return x*f;
     19 }
     20 void init()
     21 {
     22     n=read();  m=read();  k=read();//n是结点数,m是边数,k是度数限制
     23     memset(a,-1,sizeof(a));//a是图的邻接矩阵
     24     for(int i=1;i<=m;i++)
     25     {
     26         int x=read(),y=read(),v=read();
     27         e[i].x=x;  e[i].y=y;  e[i].v=v;
     28         if(a[x][y]==-1)  a[x][y]=a[y][x]=v;
     29         else a[x][y]=a[y][x]=min(a[x][y],v);  //判重边
     30     }
     31 }
     32 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}//路径压缩
     33 bool cmp(node a,node b)  {return a.v<b.v;}
     34 void Kruskal()
     35 {
     36     for(int i=1;i<=n;i++)  f[i]=i;//并查集初始化
     37     sort(e+1,e+m+1,cmp);//按边权排序
     38     for(int i=1;i<=m;i++)
     39     {
     40         if(e[i].x==1||e[i].y==1)  continue;
     41         int x=find(e[i].x),y=find(e[i].y);
     42         if(x!=y)
     43         {
     44             f[x]=y;
     45             check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;//check表示有边相连
     46             ans+=e[i].v;
     47         }
     48     }
     49 }
     50 void solve1()//计算最小m度生成树
     51 {
     52     for(int i=1;i<=n;i++)  Min[i]=INF;
     53     for(int i=2;i<=n;i++)//找出从1点连到每个连通块的最小边权
     54         if(a[1][i]!=-1)
     55         {
     56             int t=find(i);
     57             if(a[i][1]<Min[t])
     58             {
     59                 Min[t]=a[i][1];
     60                 temp[t]=i;
     61             }
     62         }
     63     for(int i=1;i<=n;i++)//把1点和每个连通块连接起来,计算答案
     64         if(Min[i]!=INF)
     65         {
     66             md++;
     67             check[1][temp[i]]=check[temp[i]][1]=1;
     68             ans+=a[1][temp[i]];
     69         }
     70 }
     71 void dfs(int x,int fa)//动规计算dp,dp记录的是从1到某点路径中权值最大的边,且此边不与1相连
     72 {
     73     for(int i=2;i<=n;i++)//枚举点
     74         if(check[x][i]&&i!=fa)//有边相连
     75         {
     76             if(dp[i].v==-1)//没被搜过,更新dp
     77             {
     78                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
     79                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
     80             }
     81             dfs(i,x);
     82         }
     83 }
     84 void solve2()//计算最小k度生成树
     85 {
     86     for(int i=md+1;i<=k;i++)
     87     {
     88         memset(dp,-1,sizeof(dp));  dp[1].v=-INF;
     89         for(int j=2;j<=n;j++)  if(check[1][j])  e[j].v=-INF;//把与1相连的边设为-oo,避免dfs时搜到
     90         dfs(1,0);  int t=0,minn=INF;
     91         for(int j=2;j<=n;j++)
     92             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)//记录对答案贡献最大的边
     93             {
     94                 minn=a[1][j]-dp[j].v;
     95                 t=j;
     96             }
     97         if(minn>=0)  break;//找不到就说明已经增广完了
     98         int x=dp[t].x,y=dp[t].y;
     99         check[1][t]=check[t][1]=1;//维护图的性质
    100         check[x][y]=check[y][x]=0;
    101         ans+=minn;
    102     }
    103 }
    104 int main()
    105 {
    106     freopen("cin.in","r",stdin);
    107     freopen("cout.out","w",stdout);
    108     init();
    109     Kruskal();
    110     solve1();
    111     solve2();
    112     printf("%d
    ",ans);
    113     return 0;
    114 }

    【例题一】poj 1639  

    题目大意:

    给出m条边,每条边有两个端点和一个权值,求这个图在满足以下条件的情况下的最小生成树:

    在所有点中,有一个特殊点Park,它在求得的最小生成树中的度必须小于等于某个值。

    这题需要注意在输入时处理字符串,把Park设为根结点,然后用上述算法即可。

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<cstdlib>
      5 #include<cmath>
      6 #include<ctime>
      7 #include<algorithm>
      8 #include<string>
      9 #include<map>
     10 using namespace std;
     11 #define INF 1000000000
     12 #define MAXN 105
     13 struct node{int x,y,v;}e[MAXN*MAXN],dp[MAXN];
     14 int n(1),m,K,oo,ans,md,Min[MAXN],temp[MAXN],f[MAXN],a[MAXN][MAXN],check[MAXN][MAXN];
     15 string s;
     16 map<string,int> Map; 
     17 inline int read()
     18 {
     19     int x=0,f=1;  char ch=getchar();
     20     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
     21     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
     22     return x*f;
     23 }
     24 bool cmp(node a,node b)  {return a.v<b.v;}
     25 int cal()  {if(Map.find(s)==Map.end())  Map[s]=++n;  return Map[s];}
     26 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}
     27 void init()
     28 {
     29     m=read();  Map["Park"]=1;  
     30     memset(a,-1,sizeof(a));  
     31     memset(Min,10,sizeof(Min));
     32     oo=Min[0];
     33     for(int i=1;i<=m;i++)
     34     {
     35         cin>>s;  e[i].x=cal();
     36         cin>>s;  e[i].y=cal();
     37         e[i].v=read();  
     38         if(a[e[i].x][e[i].y]==-1)  a[e[i].y][e[i].x]=a[e[i].x][e[i].y]=e[i].v;
     39         else a[e[i].x][e[i].y]=a[e[i].y][e[i].x]=min(a[e[i].x][e[i].y],e[i].v);
     40     }
     41     K=read();
     42 }
     43 void kruskal()
     44 {
     45     for(int i=1;i<=n;i++)  f[i]=i;
     46     sort(e+1,e+m+1,cmp);
     47     for(int i=1;i<=m;i++)
     48     {
     49         if(e[i].x==1||e[i].y==1)  continue;
     50         int x=find(e[i].x),y=find(e[i].y);
     51         if(x==y)  continue;
     52         check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;
     53         f[y]=x;
     54         ans+=e[i].v;
     55     }
     56 }
     57 void solve1()
     58 {
     59     for(int i=2;i<=n;i++)
     60         if(a[i][1]!=-1)
     61         {
     62             int t=find(i);
     63             if(a[i][1]<Min[t])
     64             {
     65                 temp[t]=i;
     66                 Min[t]=a[i][1];
     67             }
     68         }
     69     for(int i=1;i<=n;i++)
     70         if(Min[i]!=oo)
     71         {
     72             md++;
     73             check[1][temp[i]]=check[temp[i]][1]=1;
     74             ans+=a[1][temp[i]];
     75         }    
     76 }
     77 void dfs(int x,int fa)
     78 {
     79     for(int i=2;i<=n;i++)
     80         if(check[x][i]&&i!=fa)
     81         {
     82             if(dp[i].v==-1)
     83             {
     84                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
     85                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
     86             }
     87             dfs(i,x);
     88         }
     89 }
     90 void solve2()
     91 {
     92     for(int i=md+1;i<=K;i++)
     93     {
     94         memset(dp,-1,sizeof(dp));  dp[1].v=-INF;
     95         for(int j=2;j<=n;j++)  if(check[1][j])  dp[j].v=-INF;
     96         dfs(1,-1);  int t=0,minn=INF;
     97         for(int j=2;j<=n;j++)  
     98             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)  
     99             {
    100                 minn=a[1][j]-dp[j].v; 
    101                 t=j;
    102             }
    103         if(minn>=0)  break;
    104         check[1][t]=check[t][1]=1;
    105         int x=dp[t].x,y=dp[t].y;
    106         check[x][y]=check[y][x]=0;
    107         ans+=minn;
    108     }
    109 }
    110 int main()
    111 {
    112     init();
    113     kruskal();
    114     solve1();
    115     solve2();
    116     printf("Total miles driven: %d
    ",ans);
    117     return 0;
    118 }

     【例题二】poj 2349

    题目大意:

    某地区共有n座村庄,每座村庄的坐标用一对整数(x, y)表示,现在要在村庄之间建立通讯网络。通讯工具有两种,分别是需要铺设的普通线路和卫星设备。卫星设备数量有限,只能给k个村庄配备卫星设备。拥有卫星设

    备的村庄互相间直接通讯;铺设了线路的村庄之间也可以通讯。卫星分配是不受限制的。

    输入:

    第一行:数据组数CASE

    接下来每组数据第一行:k,n,分别为卫星数和村庄数。

    接下来n行,每行2个数,x,y,表示村庄的坐标。

    输出:

    最短通讯网络中最长的路。

    题解详见:2004年国家集训队论文——汪汀《最小生成树问题的扩展》

    附参考代码:

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<cstdlib>
      5 #include<cmath>
      6 #include<ctime>
      7 #include<algorithm>
      8 using namespace std;
      9 #define INF 1000000000
     10 #define MAXN 505
     11 struct node{int x,y;double v;}e[MAXN*MAXN],dp[MAXN*MAXN];
     12 int n,m,k,md,X[MAXN],Y[MAXN],f[MAXN],temp[MAXN],check[MAXN][MAXN];
     13 double ans,Min[MAXN],a[MAXN][MAXN];
     14 inline int read()
     15 {
     16     int x=0,f=1;  char ch=getchar();
     17     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
     18     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
     19     return x*f;
     20 }
     21 double cal(int a,int b)  {return sqrt(((X[a]-X[b])*(X[a]-X[b])+(Y[a]-Y[b])*(Y[a]-Y[b]))*1.00);}
     22 void insert(int x,int y,double v)
     23 {
     24     e[++m].x=x;e[m].y=y;e[m].v=v;
     25     if(a[x][y]==-1)  a[x][y]=a[y][x]=v;
     26     else a[x][y]=a[y][x]=(a[x][y]<v?v:a[x][y]);
     27 }
     28 void init()
     29 {
     30     memset(a,0,sizeof(a));
     31     k=read();  n=read();
     32     for(int i=1;i<=n;i++)
     33     {
     34         X[i]=read(),Y[i]=read();
     35         for(int j=1;j<i;j++)  insert(i+1,j+1,cal(i,j));
     36     }
     37     for(int i=1;i<=n;i++)  insert(1,i+1,0);
     38     n++;
     39 }
     40 bool cmp(node a,node b)  {return a.v<b.v;}
     41 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}
     42 void Kruskal()
     43 {
     44     for(int i=1;i<=n;i++)  f[i]=i;
     45     sort(e+1,e+m+1,cmp);
     46     for(int i=1;i<=m;i++)
     47     {
     48         if(e[i].x==1||e[i].y==1)  continue;
     49         int x=find(e[i].x),y=find(e[i].y);
     50         if(x==y)  continue;
     51         f[x]=y;
     52         check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;
     53     }
     54 }
     55 void solve1()
     56 {
     57     for(int i=1;i<=n;i++)  Min[i]=INF;
     58     for(int i=2;i<=n;i++)
     59         if(a[1][i]!=-1)
     60         {
     61             int t=find(i);
     62             if(a[i][1]<Min[t])
     63             {
     64                 Min[t]=a[i][1];
     65                 temp[t]=i;
     66             }
     67         }
     68     for(int i=1;i<=n;i++)
     69         if(Min[i]!=INF)
     70         {
     71             md++;
     72             check[1][temp[i]]=check[temp[i]][1]=1;
     73         }
     74 }
     75 void dfs(int x,int fa)
     76 {
     77     for(int i=2;i<=n;i++)
     78         if(check[x][i]&&i!=fa)
     79         {
     80             if(dp[i].v==-1)
     81             {
     82                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
     83                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
     84             }
     85             dfs(i,x);
     86         }
     87 }
     88 void solve2()
     89 {
     90     for(int i=md+1;i<=k;i++)
     91     {
     92         for(int i=0;i<=n;i++)  dp[i].v=-1;    dp[1].v=-INF;
     93         for(int j=2;j<=n;j++)  if(check[1][j])  e[j].v=-INF;
     94         dfs(1,0);  int t=0;  double minn=INF;
     95         for(int j=2;j<=n;j++)
     96             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)
     97             {
     98                 minn=a[1][j]-dp[j].v;
     99                 t=j;
    100             }
    101         if(minn>=0)  break;
    102         int x=dp[t].x,y=dp[t].y;
    103         check[1][t]=check[t][1]=1;
    104         check[x][y]=check[y][x]=0;
    105     }
    106 }
    107 void pre()
    108 {
    109     n=m=k=md=0;  ans=0;
    110     memset(check,0,sizeof(check));
    111 }
    112 int main()
    113 {
    114     //freopen("cin.in","r",stdin);
    115     //freopen("cout.out","w",stdout);
    116     int CASE=read();
    117     while(CASE--)
    118     {
    119         pre();
    120         init();
    121         Kruskal();
    122         solve1();
    123         solve2();
    124         for(int i=1;i<=n;i++)
    125             for(int j=1;j<=n;j++)
    126                 if(check[i][j]&&a[i][j]>ans)  ans=a[i][j];
    127         printf("%.2lf
    ",ans);    
    128     }
    129     return 0;
    130 }
  • 相关阅读:
    使用ADO.NET2.0提升数据交互性能 DataSet 数据表
    AD域控制器所有使用的端口明细列表
    链接数据库 远程事务的处理方式
    根据权限动态生成菜单栏和工具栏
    FTP服务器配置(cmd中ftp命令)
    该操作未能执行,因为 OLE DB 提供程序SQLOLEDB无法启动分布式事务
    ChartLet GDI+中发生一般性错误
    SQL SERVER 2000用户sa 登录失败的解决办法
    .net实例:网站发布后,在IIS中浏览提示:无法找到该页...404错误。
    Winform 关闭应用程序
  • 原文地址:https://www.cnblogs.com/chty/p/5934669.html
Copyright © 2020-2023  润新知