各种奇怪的生成树问题
最近做了5道生成树问题,现在总结一下。
- 求最大边最小边差值最小的生成树
- 次小生成树
- 有向图生成树(最小树形图)
- 最优比率生成树
- 顶点度数有限制的MST
1.求最大边最小边差值最小的生成树
题目
求一颗生成树,让最大边最小边差值最小,kruskal。
将边排序后,从最小的开始依次枚举。很暴力的做法,没想出更好的方法,百度了一下,差不多都是这个暴力的想法。
主要代码
const int maxn = 110;
struct Edge {
int from,to,len;
};
int n , m;
int father[maxn];
Edge edge[maxn * maxn ];
bool cmp ( Edge a , Edge b ) {
return a.len < b.len ;
}
int find ( int x ) {
return ( x == father[x] ) ? x : father[x] = find (father[x] );
}
int main()
{
while ( ~scanf("%d%d",&n,&m) && n+m ) {
for ( int i = 0 ; i < m ; i++ ) {
scanf("%d%d%d",&edge[i*2].from,&edge[i*2].to,&edge[i*2].len);
edge[i*2+1].from = edge[i*2].to;
edge[i*2+1].to = edge[i*2].from;
edge[i*2+1].len = edge[i*2].len;
}
m *= 2;
sort ( edge , edge + m , cmp );
int st = 0 , ans = -1;
while ( st + n - 2 < m ) {
for ( int i = 1 ; i <= n ; i++ ) father[i] = i;
int cnt = 0 , maxlen = 0 , flag = 0;
for ( int i = st ; i < m ; i++ ) {
if ( cnt == n - 1 ) {
flag = 1;
break;
}
Edge e = edge[i];
int u = find(e.from);
int v = find(e.to);
if ( u != v ) {
maxlen = max ( ans , e.len );
cnt++;
father[u] = v;
}
}
if ( flag )
ans = (ans==-1) ? maxlen-edge[st].len : min ( ans , maxlen-edge[st].len);
st++;
}
printf("%d\n",ans);
}
return 0;
}
2.次小生成树
题目
判断MST是否唯一。可以求次小生成树,然后判断次小和最小是否相等。当然,不用求出次小,在求次小的过程中,就可以判断出是否唯一。
方法有两种:
- 先求出MST。然后枚举MST的每一条边,每次删掉一条边,求MST,这些MST中的最小值,就是次小生成树
- 先求出MST。然后枚举非MST上的一条边,加上这条边一定会形成一个环,然后找到这个环上除了加上的这条边的最大边(就是这个边的两个端点在MST上的路径中的最大边),减去这个最大边,加上新边,就是一颗新树。所有新树中的最小值,就是次小生成树。最大边可以用BFS或DFS预处理。
我采用的是第二种,写的很挫。
主要代码
const int maxn = 110;
struct Edge {
int id;
int from , to , len;
int flag;
Edge() {flag = 0;}
Edge( int from , int to , int len ) :
from(from),to(to),len(len) { flag = 0; }
bool operator < ( const Edge& rhs ) const {
return len > rhs.len ;
}
};
vector edges;
vector G[maxn];
int n,m;
int vis[maxn] , dis[maxn] , nxt[maxn] , nxtlen[maxn];
vector mst[maxn];
int f[maxn][maxn];
void AddEdge ( int x , int y , int z ) {
edges.pb( Edge(x,y,z) );
int m = sz(edges);
edges[m-1].id = m-1;
edges[m-1].flag = 0;
G[x].pb( sz(edges) - 1 );
}
void init()
{
scanf("%d%d",&n,&m);
for ( int i = 1 ; i <= n ; i++ ) G[i].clear();
for ( int i = 1 ; i <= n ; i++ ) mst[i].clear();
edges.clear();
for ( int i = 1 ; i <= m ; i++ ) {
int x , y , z ;
scanf("%d%d%d",&x,&y,&z);
AddEdge ( x , y , z );
AddEdge ( y , x , z );
}
}
int MST() {
priority_queue q;
int ans = 0;
clr(vis);
for ( int i = 0 ; i < sz(G[1]) ; i++ ) {
Edge& e = edges[G[1][i]] ;
int v = e.to;
if ( dis[v] > e.len ) {
dis[v] = e.len;
Edge ee;
ee.from = 1; ee.to = v ; ee.len = dis[v] ; ee.id = e.id;
q.push(ee);
}
}
vis[1] = 1;
while ( !q.empty() ) {
Edge e = q.top(); q.pop();
int u = e.to;
if ( vis[u] ) continue;
vis[u] = 1;
ans += dis[u];
e.flag = 1;
mst[e.from].pb( mp ( e.to , e.len ) );
mst[e.to].pb( mp ( e.from , e.len ));
edges[e.id].flag = 1;
edges[e.id^1].flag = 1;
for ( int i = 0 ; i < sz ( G[u] ) ; i++ ) {
Edge& e = edges[ G[u][i] ];
if ( !vis[e.to] && dis[e.to] > e.len ) {
dis[e.to] = e.len;
Edge ee;
ee.from = u ; ee.to = e.to; ee.len = e.len ; ee.id = e.id;
q.push(ee);
}
}
}
return ans;
}
void BFS ( int s ) {
queue q;
clr(vis);
q.push(s);
vis[s] = 1;
while ( !q.empty() ) {
int u = q.front(); q.pop();
for ( int i = 0 ; i < sz(mst[u]) ; i++ ) {
int v = mst[u][i].first;
f[s][v] = max ( f[s][u] , mst[u][i].second );
if ( !vis[v] ) { q.push(v); vis[v] = 1; }
}
}
}
int main()
{
// freopen("a.in","r",stdin);
int T;
scanf("%d",&T);
while ( T-- ) {
init();
memset(nxt,-1,sizeof(nxt));
memset(nxtlen,-1,sizeof(nxtlen));
for ( int i = 1 ; i <= n ; i++ ) dis[i] = inf;
int ans = MST();
memset( f , -1 , sizeof(f) );
for ( int i = 1 ; i <= n ; i++ ) BFS(i);
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 1 ; j <= n ; j++ )
if ( f[i][j] != -1 ) f[j][i] = f[i][j];
int flag = 0;
for ( int i = 0 ; i < sz(edges) ; i++ ) {
Edge& e = edges[i];
if ( e.flag == 0 && e.len == f[e.from][e.to] ) {
flag = 1;
break;
}
}
if ( flag )
printf("Not Unique!\n");
else
printf("%d\n",ans);
}
return 0;
}
3.有向图生成树(最小树形图)
题目
最小树形图的模板题。我直接用了模板。
关于算法的讲解,请点这里
主要代码
- 邻接矩阵
const int VN = 105;
const int INF = 0x7fffffff;
template
class Directed_MST{
public:
void init(int _n){
n=_n;
ans = 0;
memset(vis, 0, sizeof(vis));
memset(inc, 0, sizeof(inc));
for(int i=0; i <= n; ++i){
w[i][i] = INF;
for(int j=i+1; j<=n; ++j)
w[i][j]=w[j][i]=INF;
}
}
void insert(int u, int v, Type _w){
if(w[u][v]>_w) w[u][v] = _w;
}
Type directed_mst(int u){
//== 步骤1: 判断能否形成最小树形图,直接dfs遍历
dfs(u);
for(int i=1; i <= n; ++i)
if(!vis[i]) { return -1; }
//== 如果可以形成最小树形图,继续
memset(vis, 0, sizeof(vis));
while(true){
//== 1. 找最小前驱边
for(int i=1; i <= n; ++i)if(i!=u&&!inc[i]){
w[i][i]=INF, pre[i] = i;
for(int j=1; j<=n; ++j)if(!inc[j] && w[j][i]<w[pre[i]][i]){ pre[i]="j;" }="" =="2.判断是否有环" int="" i;="" for(i="1;" i="" <="n;" ++i)if(i!="u&&!inc[i]){" j="i," cnt="0;" while(j!="u" &&="" pre[j]!="i" ++cnt;="" if(j="=u" ||="">n) continue; //没找到
break;
}
//== 没有找到环,得到答案
if(i>n){
for(int i=1; i <= n; ++i)if(i!=u && !inc[i]) ans+=w[pre[i]][i];
return ans;
}
//== 有环,进行收缩
int j=i;
memset(vis, 0, sizeof(vis));
do{
ans += w[pre[j]][j], j=pre[j], vis[j]=inc[j]=true;
}while(j!=i);
inc[i] = false; // 环缩成了点i,点i仍然存在
//== 收缩
for(int k=1; k <= n; ++k)if(vis[k]){ // 在环中点点
for(int j=1; j <= n; ++j)if(!vis[j]){ // 不在环中的点
if(w[i][j] > w[k][j]) w[i][j] = w[k][j];
if(w[j][k] < INF && w[j][k]-w[pre[k]][k] < w[j][i])
w[j][i] = w[j][k] - w[pre[k]][k];
}
}
}
return ans;
}
private:
// 从根结点遍历一遍,判断是否存在最小树形图
void dfs(int u){
vis[u] = true;
for(int i=1; i <= n; ++i)if(!vis[i]&&w[u][i] < INF){
dfs(i);
}
}
private:
Type ans; // 所求答案
int n; // 结点个数
int pre[VN]; // 权值最小的前驱边
bool vis[VN]; // 是在环中还是在环外
bool inc[VN]; // 该点是否被删除了(收缩)
Type w[VN][VN]; // 图
};
- 邻接表
struct node
{
int u, v;
type w;
}edge[MAXN * MAXN];
int pre[MAXN], id[MAXN], vis[MAXN], n, m;
type in[MAXN];
type Directed_MST(int root, int V, int E)
{
type ret = 0;
while(true)
{
//1.找最小入边
for(int i = 0; i < V; i++)
in[i] = INF;
for(int i = 0; i < E; i++)
{
int u = edge[i].u;
int v = edge[i].v;
if(edge[i].w < in[v] && u != v)
{pre[v] = u; in[v] = edge[i].w;}
}
for(int i = 0; i < V; i++)
{
if(i == root) continue;
if(in[i] == INF) return -1;//除了根以外有点没有入边,则根无法到达它
}
//2.找环
int cnt = 0;
memset(id, -1, sizeof(id));
memset(vis, -1, sizeof(vis));
in[root] = 0;
for(int i = 0; i < V; i++) //标记每个环
{
ret += in[i];
int v = i;
while(vis[v] != i && id[v] == -1 && v != root) //每个点寻找其前序点,要么最终寻找至根部,要么找到一个环
{
vis[v] = i;
v = pre[v];
}
if(v != root && id[v] == -1)//缩点
{
for(int u = pre[v]; u != v; u = pre[u])
id[u] = cnt;
id[v] = cnt++;
}
}
if(cnt == 0) break; //无环 则break
for(int i = 0; i < V; i++)
if(id[i] == -1) id[i] = cnt++;
//3.建立新图
for(int i = 0; i < E; i++)
{
int u = edge[i].u;
int v = edge[i].v;
edge[i].u = id[u];
edge[i].v = id[v];
if(id[u] != id[v]) edge[i].w -= in[v];
}
V = cnt;
root = id[root];
}
return ret;
}
4.最优比率生成树
题目
求最优比例生成树。
关于分数规划,可以看论文 《最小割在信息学竞赛中的应用》 ,还有黑书
有两种方法,一种是二分,一种是Dinkelbach算法。
此题如果用二分,生成树要用O(n)的算法才不会超时,所以用Dinkelbach了。
主要代码
- 二分 (TLE)
const int maxn = 1010;
struct Edge {
int from , to;
double b,c;
double d;
Edge(){}
Edge( int u , int v , double b , double c ) :
from(u) , to(v) , b(b) , c(c) {}
bool operator < ( const Edge& rhs ) const {
return d > rhs.d;
}
};
int x[maxn] , y[maxn] , z[maxn];
vector edges;
vector G[maxn];
double dis[maxn];
int n;
double dist ( int u , int v ) {
return sqrt( sqr ( x[u] - x[v] ) + sqr ( y[u] - y[v] ) );
}
void AddEdge ( int u , int v ) {
edges.pb( Edge( u , v , dist(u,v) , fabs(z[u]-z[v]) ) );
int m = sz(edges);
G[u].pb(m-1);
}
double MST ( double l ) {
for ( int i = 0 ; i < sz(edges) ; i++ ) edges[i].d = edges[i].c - edges[i].b * l;
priority_queue q;
double ans = 0;
int vis[maxn];
clr(vis);
for ( int i = 0 ; i < sz ( G[1] ) ; i++ ) {
Edge& e = edges[ G[1][i] ];
int v = e.to;
if ( dis[v] > e.d ) {
dis[v] = e.d;
Edge ee;
ee.from = 1; ee.to = v ; ee.d = e.d;
q.push(ee);
}
}
vis[1] = 1;
while ( !q.empty() ) {
Edge e = q.top() ; q.pop();
int u = e.to;
if ( vis[u] ) continue;
vis[u] = 1;
ans += dis[u];
for ( int i = 0 ; i < sz ( G[u] ) ; i++ ) {
Edge& e = edges[ G[u][i] ] ;
int v = e.to;
if ( !vis[v] && dis[v] > e.d ) {
dis[v] = e.d;
Edge ee;
ee.from = 1; ee.to = v ; ee.d = e.d;
q.push(ee);
}
}
}
return ans;
}
int init()
{
scanf("%d",&n);
if ( n == 0 ) return 0;
for ( int i = 1 ; i <= n ; i++ ) scanf("%d%d%d",&x[i],&y[i],&z[i]) , G[i].clear();
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 1 ; j <= n ; j++ )
if ( i != j ) AddEdge ( i , j );
return 1;
}
int main()
{
while ( init() ) {
double l = 0.0 , r = 10000.0;
while ( l + eps < r ) {
double mid = ( l + r ) / 2;
for ( int i = 1 ; i <= n ; i++ ) dis[i] = inf;
double ans = MST(mid);
if ( ans > 0 ) l = mid;
else r = mid;
}
printf("%.3lf\n",l);
}
return 0;
}
- Dinkelbach (Memory: 52556 KB Time: 2407 MS 也是好悬的飘过啊)
const int maxn = 1010;
struct Edge {
int from , to;
double b,c;
double d;
Edge(){}
Edge( int u , int v , double b , double c ) :
from(u) , to(v) , b(b) , c(c) {}
bool operator < ( const Edge& rhs ) const {
return d > rhs.d;
}
};
int x[maxn] , y[maxn] , z[maxn];
vector edges;
vector G[maxn];
double dis[maxn];
int n;
double dist ( int u , int v ) {
return sqrt( sqr ( x[u] - x[v] ) + sqr ( y[u] - y[v] ) );
}
void AddEdge ( int u , int v ) {
edges.pb( Edge( u , v , dist(u,v) , fabs(z[u]-z[v]) ) );
int m = sz(edges);
G[u].pb(m-1);
}
double MST ( double l ) {
for ( int i = 0 ; i < sz(edges) ; i++ ) edges[i].d = edges[i].c - edges[i].b * l;
priority_queue q;
double ans = 0 , p = 0 , qq = 0;
int vis[maxn];
clr(vis);
for ( int i = 0 ; i < sz ( G[1] ) ; i++ ) {
Edge& e = edges[ G[1][i] ];
int v = e.to;
if ( dis[v] > e.d ) {
dis[v] = e.d;
Edge ee;
ee.from = 1; ee.to = v ; ee.c = e.c ; ee.b = e.b; ee.d = e.d;
q.push(ee);
}
}
vis[1] = 1;
while ( !q.empty() ) {
Edge e = q.top() ; q.pop();
int u = e.to;
if ( vis[u] ) continue;
vis[u] = 1;
ans += dis[u];
p += e.c;
qq += e.b;
for ( int i = 0 ; i < sz ( G[u] ) ; i++ ) {
Edge& e = edges[ G[u][i] ] ;
int v = e.to;
if ( !vis[v] && dis[v] > e.d ) {
dis[v] = e.d;
Edge ee;
ee.from = 1; ee.to = v ; ee.b =e.b; ee.c =e.c; ee.d = e.d;
q.push(ee);
}
}
}
double ll = p/qq;
if ( fabs ( l - ll ) > eps ) return ll;
else {
printf("%.3f\n",l);
return 0;
}
}
int init()
{
scanf("%d",&n);
if ( n == 0 ) return 0;
edges.clear();
for ( int i = 1 ; i <= n ; i++ ) scanf("%d%d%d",&x[i],&y[i],&z[i]) , G[i].clear();
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 1 ; j <= n ; j++ )
if ( i != j ) AddEdge ( i , j );
return 1;
}
int main()
{
while ( init() ) {
for ( int i = 1 ; i <= n ; i++ ) dis[i] = inf;
double l = MST(0.0);
while ( sgn(l) != 0 ) {
for ( int i = 1 ; i <= n ; i++ ) dis[i] = inf;
l = MST(l);
}
}
return 0;
}
5.顶点度数有限制的MST
题目
资料可以看黑书,还有论文《最小生成树问题的拓展》
具体步骤如下:
- 删除V0点,求最小生成森林,会得到 m 个连通分量。 求每个连通分量上距离V0最近的点。
- 这样求得 m 度最小生成树 ,等于每个森林的和,再加上每个分量到V0的最近距离。
- 对 生成树 进行 k-m 次 可行操作。 (若 k < m , 则无解)
- 可行操作:枚举每一条V0出发而不在当前树上的边,记另一点为Vx。 找到所有符合条件的Vx中,length(V0,Vx) - maxcost(V0) 最小的 Vx。 连接V0和Vx ,删去maxcost的边。 得到新树,重复操作。
- maxcost(Vx) 为Vx到V0在当前树的路径中的最大边。可以用DP预处理出来。
主要代码
这题现在还没AC。敲代码太不稳了。总是WA。
Author
- Author : Praesidio
- Time : 2014-8-19 22:24:47