题意:求最小生成树,与1连接的边有k条。输出连的边的编号
思路:我们先把除1外的点作最小生成树,再把1加进去(通过1把之前的集合连接起来)。加完之后,如果集合 大于 1,无解;如果新加的边 大于 k,无解;如果,从1出去的边的数量,比k还小,无解。
接下来就是,用从1出去的边1去替换那些第一次作最小生成树时候加的边。新加边,最优的情况是 新加边的权 减 去掉边的权 的差最小(增加值最小)。新加一条边,就会形成一个环,环中权最大的边就是加这条边进去最优。每添加一条边,都需要对环上的边一一枚举,太慢了。我们可以用dp记录下,在这个点加一条边,形成的环中最大边权的编号。每次一个点与1连接,就对它dfs,求dp。每次新加一条边,直到k。
也许讲的比较懵,参见 http://www.cnblogs.com/ylfdrib/archive/2010/08/21/1805505.html
部分引用论文:
第一步求解最小m度限制生成树:原图中去掉和V0相连的所有边,得到m个连通分量,而这m 个连通分量必须通过v0来连接,所以,在图G 的所有生成树中dT(v0)≥m。也就是说,当k<m时,问题无解。对每个连通分量求一次最小生成树,对于每个连通分量V’,求一点v1,v1∈V',且ω(v0,v1)=min{ω(v0,v')|v'∈V'},则该连通分量通过边(v1,v0)与v0相连。于是,我们就得到了一个m度限制生成树,不难证明,这就是最小m度限制生成树。
第二步,由最小m度限制生成树,得到最小m+1度限制生成树,对于和V0相邻的点v,则可以知道一定会有一个环出现,只要找到这个环上的最大权边,用边(V0, v)替换掉,就可以得到一个m+1度限制生成树,枚举所有和V0相邻点v,找到替换后增加权值最小的一次替换,就可以求得m+1度限制生成树。。如果每添加一条边,都需要对环上的边一一枚
举,时间复杂度将比较高,这里,动态规划就有了用武之地。设Best(v)为路径v0—v上与v0无关联且权值最大的边。定义father(v)为v的父结点,动态转移方程:Best(v)=max(Best(father(v)),ω(father(v),v)),边界条件为Best[v0]=-∞,Best[v’]=-∞| (v0,v’)∈E(T)。
第三步, 当度为K的时候就可以退出了。
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<stdlib.h>
#include<math.h>
#include<vector>
#include<list>
#include<map>
#include<stack>
#include<queue>
#include<algorithm>
#include<numeric>
#include<functional>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 5005;
int pre[maxn],dp[maxn];
struct data
{
int x,y,cost,id;
};
struct Edge
{
int x,y,cost;
}edge[100005];
vector<data> st,p,en;
int vis[100005],ans[100005],tot;
int line[maxn][maxn],n;
int cmp(const data &a,const data &b)
{
return a.cost < b.cost;
}
void init(int n)
{
for(int i = 1; i <= n; i++)
pre[i] = i;
st.clear();
en.clear();
p.clear();
memset(vis,0,sizeof vis);
memset(line,0,sizeof line);
}
int fid(int x)
{
if(pre[x] == x)
return x;
else
return pre[x] = fid(pre[x]);
}
int dfs(int x,int pre,int id) //算环中边权最大id
{
if(edge[id].cost >= edge[ dp[pre] ].cost)
dp[x] = id;
else
dp[x] = dp[pre];
for(int i = 1; i <= n; i++)
{
if(line[x][i] && i != pre)
dfs(i,x,line[i][x]);
}
}
int main(void)
{
int m,k;
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
init(n);
for(int i = 1; i <= m; i++) //st存与1连接的边
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
edge[i] = (Edge){a,b,c};
if(a == 1)
st.push_back((data){a,b,c,i});
else if(b == 1)
st.push_back((data){b,a,c,i});
else
p.push_back((data){a,b,c,i});
}
sort(p.begin(),p.end(),cmp);
sort(st.begin(),st.end(),cmp);
for(int i = 0; i < p.size(); i++) //除1外,作最小生成树
{
int x = p[i].x;
int y = p[i].y;
int nx = fid( x );
int ny = fid( y );
if(nx != ny)
{
pre[nx] = ny;
line[x][y] = line[y][x] = p[i].id;
vis[p[i].id] = 1;
}
}
int num = 0;
for(int i = 0; i < st.size(); i++) //通过1,连接它们
{
int x = st[i].x;
int y = st[i].y;
int nx = fid( x );
int ny = fid( y );
if(nx != ny)
{
pre[ny] = nx;
dfs(y,0,0);
vis[st[i].id] = 1;
num++;
}
}
int block = 0;
for(int i = 1; i <= n; i++)
{
if(fid(i) == i)
block++;
}
if(block > 1 || st.size() < k || num > k)
{
printf("-1\n");
continue;
}
num = k - num;
while(num--) //替换边
{
for(int i = 0; i < st.size(); i++)
{
if( !vis[ st[i].id ] )
{
int y = st[i].y;
en.push_back((data){st[i].id,dp[y],
st[i].cost-edge[ dp[y] ].cost,y});
//add id,del id,增加量,新连的点
}
}
sort(en.begin(),en.end(),cmp);
vis[ en[0].x ] = 1;
vis[ en[0].y ] = 0;
line[ edge[en[0].y].x ][ edge[en[0].y].y ] = line[ edge[en[0].y].y ][ edge[en[0].y].x ] = 0;
dfs(en[0].id,0,0);
en.clear();
}
tot = 0;
for(int i = 1; i <= m; i++)
{
if(vis[i])
ans[tot++] = i;
}
printf("%d\n",tot);
for(int i = 0 ; i < tot; i++)
printf("%d%c",ans[i],i==tot-1?'\n':' ');
}
return 0;
}