题目链接

最小k度生成树

题意:以Park为1号点,求1号点度数不超过s的最小生成树是多少,点数不超过30。

最小k度生成树的求法:
假设限制度数的点为v0,除去v0后,剩下的点会构成m个连通块,如果限制的度数小于m,显然是无解的。
对除去v0后的所有连通块求最小生成树,在这m个最小生成树中,分别选择一条与v0相连且权值最小的边加入到生成树中,构成的生成树就是最小m度生成树。
如果要求m+1度的生成树,只需要选取一条最小且在生成树以外的边,加入到生成树中,此时的生成树会产生一个环,除去环中权值最大的边,就是最小m+1度生成树,最小m+2度生成树也是同理(在m+1度的基础上求)
把v0设置为根节点,环中最大的边,可以通过dp记录根节点到当前节点中权值最大的边是多少,取环中最大权值就能O(1)取,O(V)预处理。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;

typedef long long ll;

const int MaxnN = 200+10;
const int MaxnM = 2e5+10;
const int INF = 0x3f3f3f3f;
const ll LINF = 1e18;
struct Edge {
	int u, v, w, next;
	bool used;  // 是否为生成树的边 
} edge[MaxnM]; 
struct Node {
	int v, w, i;
	Node(int _v, int _w, int _i):v(_v), w(_w), i(_i){}
	bool operator < (const Node &a1) const {
		return w > a1.w;
	}
};
// dp 记录根节点到当前节点中的最大边是多少, pos为相应边的下标
int h[MaxnN], edge_cnt, dp[MaxnN], pos[MaxnN];  
int m, s, v0, tot = 0;
map <string, int> mp;
bool vis[MaxnN];
priority_queue <Node> qu;
void add(int u, int v, int w) {
	edge[edge_cnt].u = u; edge[edge_cnt].v = v;
	edge[edge_cnt].w = w; edge[edge_cnt].used = false;
	edge[edge_cnt].next = h[u]; 
	h[u] = edge_cnt++;
}
// 边划分连通块边求连通块的最小生成树,并算出生成树中
// 离根节点v0的最近的边是多少
void prim(int u, int& ans) {
	while(!qu.empty()) qu.pop();
	int cost = INF, E; 
	qu.push(Node(u, 0, -1));
	
	while(!qu.empty()) {
		Node cur = qu.top(); qu.pop();
		u = cur.v;
		if(vis[u]) continue;
		vis[u] = true; ans += cur.w;
		if(cur.i != -1) edge[cur.i].used = edge[cur.i^1].used = true;
		
		for(int i = h[u]; i != -1; i = edge[i].next) {
			Edge &e = edge[i];
			if(!vis[e.v] && e.v != v0) qu.push(Node(e.v, e.w, i));
			else if(e.v == v0 && cost > e.w) { // 如果是与根节点的边就记录,取最小值 
				cost = e.w; E = i;
			}
		}
	}
	if(cost != INF) { // 连通块与v0最近的边直接加入到生成树中 
		ans += cost; 
		edge[E].used = edge[E^1].used = true;
	}
}

void dfs(int u) { 
	for(int i = h[u]; i != -1; i = edge[i].next) {
		Edge &e = edge[i];
		if(e.used && dp[e.v] == -INF) {
			dp[e.v] = dp[u]; pos[e.v] = pos[u];
			if(dp[e.v] < e.w) {
				dp[e.v] = e.w; pos[e.v] = i;
			}
			dfs(e.v);
		} 
	}
}

int main(void)
{
	int w, x, y;
	char u[50], v[50];
	scanf("%d", &m);
	for(int i = 0; i < MaxnN; ++i) h[i] = -1;
	edge_cnt = 0; 
	
	for(int i = 0; i < m; ++i) {
		scanf("%s%s%d", u, v, &w);
		if(mp.find(u) == mp.end()) mp[u] = tot++;
		if(mp.find(v) == mp.end()) mp[v] = tot++;
		x = mp[u]; y = mp[v];
		add(x, y, w); add(y, x, w);
	}
	scanf("%d", &s);
	memset(vis, false, sizeof(vis));
	v0 = mp["Park"];
	int cnt = 0, ans = 0;
	for(int i = 0; i < tot; ++i) {
		if(!vis[i] && i != v0) {
			prim(i, ans); cnt++;	
		}
	}
	int tmp = ans;
	for(int i = cnt+1; i <= s; ++i) {
		// 生成树有删边和加边的情况,所以每次dp都需要重新算 
		for(int j = 0; j < tot; ++j) dp[j] = -INF;
		dp[v0] = -1; dfs(v0);
		int cost = INF, E, v1;
		for(int j = h[v0]; j != -1; j = edge[j].next) {
			Edge &e = edge[j];
			if(!e.used && cost > e.w-dp[e.v]) {
				cost = e.w-dp[e.v]; E = j; v1 = e.v;
			}
		}
		if(cost != INF) {
			tmp += cost;
			edge[E].used = edge[E^1].used = true;
			edge[pos[v1]].used = edge[pos[v1]^1].used = false;
			ans = min(ans, tmp);
		}
	}
	printf("Total miles driven: %d\n", ans);
	return 0;
 }