【题目链接】https://www.luogu.com.cn/problem/UVA1537
【题目大意】
给定一张N个点M条边的无向图,求出无向图的一棵最小生成树,满足一号节点的度数不超过给定的整数s。保证 N <= 30
【分析】
实际上本题是求一个最小度限制生成树,什么是最小度限制生成树,就是指无向图中某一个特定的结点的度等于一个指定的数值且权值和最小的一棵生成树。
用数学语言来描述:设图
G
=
(
V
,
E
,
ω
)
G=(V,E,\omega)
G=(V,E,ω),有
v
0
∈
V
v_0 \in V
v0∈V是特别指定的一个顶点,
k
k
k为给定的一个正整数。如果
T
T
T是
G
G
G的一棵生成树且
d
T
(
v
0
)
=
k
d_T(v_0) = k
dT(v0)=k,则称
T
T
T为
G
G
G的k度限制树。
G
G
G中权值和最小的k度限制树称为
G
G
G的最小k度限制树。
为了求最小k度限制树,首先要求出最小m度限制树。
删除
v
0
v_0
v0点后,图中可能会出现m个连通分量,求这m个连通分量的最小生成树。这m个连通分量必须通过
v
0
v_0
v0连接,也就是说这颗生成树的
v
0
v_0
v0点至少是m度。若m>k,问题无解。否则,一步步枚举从m到k的最小生成树。
算法框架
1.求最小m度限制生成树
2.由最小m度限制生成树得到最小m+1度限制生成树
3.当 d T ( v 0 ) = k d_T(v_0)=k dT(v0)=k时停止
先考虑第一步,删除图中与
v
0
v_0
v0相连的所有边,求各个连通分量的最小生成树;接下来对于每个连通分量,找最小的一条与
v
0
v_0
v0直接相连的边。得到了最小m度限制树
Kruskal算法实现。
void mDegreeMST()
{ //求连通块最小生成树
int x,y;
sort(edge+1,edge+n+1);
for(int i=1;i<=cnt;i++)
fa[i] = i;
for(int i=1;i<=n;i++)
{
if(edge[i].u == 1 || edge[i].v == 1)//不与根相连
continue;
x = find(edge[i].u);y = find(edge[i].v);
if(x == y) continue;
fa[x] = y;
sz+=edge[i].w;
mdeg[edge[i].u][edge[i].v]=mdeg[edge[i].v][edge[i].u] = 1;//最小m度限制生成树
}
int Min[maxn],tmp[maxn];
memset(Min,0x3f,sizeof(Min));
memset(tmp,0,sizeof(tmp));
for(int i=2;i<=cnt;i++)//连通块找最小边连根节点
{
if(mp[1][i]<0x3f3f3f3f)//是否和根节点直接相连
{
x = find(i);//属于哪个连通块
if(mp[1][i] < Min[x])
Min[x] = mp[i][1],tmp[x] = i;
}
}
for(int i=2;i<=cnt;i++)//连边
{
if(Min[i]<0x3f3f3f3f)
{
md++;
mdeg[1][tmp[i]]=mdeg[tmp[i]][1] = 1;
sz+=mp[1][tmp[i]];
}
}
}
接下来求最小m+1度限制生成树。
最小m+1度限制生成树肯定是由最小m度限制生成树经过一次边交换可得。v和
v
0
v_0
v0直接相连的边,连接
<
v
0
,
v
>
<v_0,v>
<v0,v>后可成环,删除环上最大边,就可以得到一个m+1度限制生成树。枚举所有的v,找增加边权最小的m+1度生成树。
如果考虑一一枚举,再从所有可行交换中取最小值,时间复杂度O(VE),本题数据可过,但是有更优的办法。
枚举时可以发现,有不少边被重复统计,如上图红色边。使用动态规划
B
e
s
t
(
v
)
Best(v)
Best(v)记录路径
v
0
v_0
v0到
v
v
v上与
v
0
v_0
v0无关联且边权最大的边,fa是v的父结点,
B
e
s
t
(
v
)
=
B
e
s
t
(
f
a
)
.
w
>
m
p
[
f
a
]
[
v
]
?
B
e
s
t
(
f
a
)
:
(
E
)
{
f
a
,
v
,
m
p
[
f
a
]
[
v
]
}
Best(v) = Best(fa).w>mp[fa][v] ? Best(fa) : (E)\{fa,v,mp[fa][v]\}
Best(v)=Best(fa).w>mp[fa][v]?Best(fa):(E){fa,v,mp[fa][v]},状态共|V|个,状态转移时间复杂度O(1),总时间复杂度O(V)
E Best[maxn];
void dfs(int x,int fa)//dp计算从1到某点路径中边权最大,且该边不与1相连
{
for(int i=2;i<=cnt;i++)
{
if(mdeg[x][i] && i!=fa)//在MST上
{
if(mp[x][i] < Best[x].w)
Best[i]=Best[x];
else
Best[i] = (E){x,i,mp[x][i]};
dfs(i,x);
}
}
}
void kDegreeMST()//计算最小s度限制生成树
{
for(int i = md+1;i<=s;i++)
{
memset(Best,0,sizeof(Best));
dfs(1,0);
int tmp = 0x3f3f3f3f,num;
for(int j=2;j<=cnt;j++)
{
if(mp[1][j] <0x3f3f3f3f && mp[1][j] - Best[j].w < tmp)
{
tmp = mp[1][j] - Best[j].w;
num = j;
}
}
if(tmp>=0) break;
mdeg[1][num] = mdeg[num][1] = 1;
mdeg[Best[num].u][Best[num].v] = mdeg[Best[num].v][Best[num].u] = 0;
sz += tmp;
}
printf("Total miles driven: %d",sz);
}
具体实现用unordered<string,int> id实现人名、公园和编号的映射
细节实现上还可以优化,比如建树采用静态邻接表,不过点数才20,瞎搞能过
#include <bits/stdc++.h>
using namespace std;
const int maxn = 305;
struct E
{
int u,v,w;
friend bool operator < (const E &x,const E &y)
{
return x.w < y.w;
}
};
unordered_map<string,int> id;//cnt给编号赋值
int n,s,cnt = 0;//n是边数,cnt是点数
int mp[maxn][maxn];
E edge[maxn];//tot记录边数
void read()
{
memset(mp,0x3f,sizeof(mp));
int w;
string a,b;
id["Park"] = ++cnt;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin >> a >> b >> w;
if(!id[a]) id[a] = ++cnt;
if(!id[b]) id[b] = ++cnt;
mp[id[a]][id[b]] = mp[id[b]][id[a]] = min(mp[id[a]][id[b]],w);
edge[i] = (E){id[a],id[b],w};
}
scanf("%d",&s);
}
bool mdeg[maxn][maxn];
int fa[maxn],sz=0,md=0;//md:当前根节点度数
int find(int x)
{
return x==fa[x]?x:fa[x] = find(fa[x]);
}
void mDegreeMST()
{ //求连通块最小生成树
int x,y;
sort(edge+1,edge+n+1);
for(int i=1;i<=cnt;i++)
fa[i] = i;
for(int i=1;i<=n;i++)
{
if(edge[i].u == 1 || edge[i].v == 1)//不与根相连
continue;
x = find(edge[i].u);y = find(edge[i].v);
if(x == y) continue;
fa[x] = y;
sz+=edge[i].w;
mdeg[edge[i].u][edge[i].v]=mdeg[edge[i].v][edge[i].u] = 1;//最小m度限制生成树
}
int Min[maxn],tmp[maxn];
memset(Min,0x3f,sizeof(Min));
memset(tmp,0,sizeof(tmp));
for(int i=2;i<=cnt;i++)//连通块找最小边连根节点
{
if(mp[1][i]<0x3f3f3f3f)//是否和根节点直接相连
{
x = find(i);//属于哪个连通块
if(mp[1][i] < Min[x])
Min[x] = mp[i][1],tmp[x] = i;
}
}
for(int i=2;i<=cnt;i++)//连边
{
if(Min[i]<0x3f3f3f3f)
{
md++;
mdeg[1][tmp[i]]=mdeg[tmp[i]][1] = 1;
sz+=mp[1][tmp[i]];
}
}
}
E Best[maxn];
void dfs(int x,int fa)//dp计算从1到某点路径中边权最大,且该边不与1相连
{
for(int i=2;i<=cnt;i++)
{
if(mdeg[x][i] && i!=fa)//在MST上
{
if(mp[x][i] < Best[x].w)
Best[i]=Best[x];
else
Best[i] = (E){x,i,mp[x][i]};
dfs(i,x);
}
}
}
void kDegreeMST()//计算最小s度限制生成树
{
for(int i = md+1;i<=s;i++)
{
memset(Best,0,sizeof(Best));
dfs(1,0);
int tmp = 0x3f3f3f3f,num;
for(int j=2;j<=cnt;j++)
{
if(mp[1][j] <0x3f3f3f3f && mp[1][j] - Best[j].w < tmp)
{
tmp = mp[1][j] - Best[j].w;
num = j;
}
}
if(tmp>=0) break;
mdeg[1][num] = mdeg[num][1] = 1;
mdeg[Best[num].u][Best[num].v] = mdeg[Best[num].v][Best[num].u] = 0;
sz += tmp;
}
printf("Total miles driven: %d",sz);
}
int main()
{
read();
mDegreeMST();
kDegreeMST();
return 0;
}