题目大意:
有一个公园,最多只能允许有K条路通向公园,问在这个限制条件下,所能形成的最小生成树。
资料:
IOI2004国家集训队论文--王汀《最小生成树问题的扩展》
http://wenku.baidu.com/view/41800d66ddccda38376bafac.html
度限制最小生成树的算法的关键是动态规划来实现更新每一个点到根节点的最大边。(具体实现见代码)
/* ID: [email protected] PROG: LANG: C++ 度限制最小的生成树: 1. 去掉根节点,生成一个森林。 2. 将每个子树分别于根节点相连(取最小的边)。生成一个m度最小生成树 3. 迭代(k - m)次,每次连接一条没用过的与根节点相连的边,形成一个环,去掉其中不与根节点相连的边 在更新每一个点到根节点的最大值时,可以采用动态规划的算法 mx[v] = max(mx[pre[v]], edge[v][pre[v]]) 复杂度 O(VlogV + k * n) */ #include<map> #include<set> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<vector> #include<string> #include<fstream> #include<cstring> #include<ctype.h> #include<iostream> #include<algorithm> #define maxn 105 #define maxm 100005 #define INF (1<<30) #define PI acos(-1.0) #define mem(a, b) memset(a, b, sizeof(a)) #define For(i, n) for (int i = 0; i < n; i++) typedef long long ll; using namespace std; int m, n, head[maxn], cnt, k, vis[maxn]; int cnt_block, ans, belong[maxn], pre[maxn], dis[maxn], use[maxn][maxn], link[maxn], point[maxn]; //cnt_block记录连通块的数量,belong[]记录每个点属于第几个连通分量, pre[]记录生成森林时的前驱, dis[]记录每个点到该连通分量根节点距离 //use[][]记录该条边是否使用,link[]记录每一连通分量到根节点的最小距离,point[]记录每个连通分量中与根节点相连的点 vector<int> block[maxn]; map<string, int> mp; struct node { int v, w, next; }g[maxm]; struct Edge { int u, v, w; Edge() {} Edge(int a, int b, int c) { u = a, v = b, w = c; } void init() { w = 0; } bool operator > (const Edge & a) const { return w > a.w; } }mx[maxn]; int getId(char s[]) { if (mp.find(s) == mp.end()) mp[s] = ++n; else return mp[s]; return n; } void addedge(int u, int v, int w) { g[cnt].v = v; g[cnt].w = w; g[cnt].next = head[u]; head[u] = cnt++; } void dfs(int v) { //将图分成若干连通块 vis[v] = 1; belong[v] = cnt_block; block[cnt_block].push_back(v); for (int i = head[v]; i != -1; i = g[i].next) if (!vis[g[i].v]) dfs(g[i].v); } void prim(int cur) { dis[block[cur][0]] = 0; for (int i = 1; i <= block[cur].size(); i++) { int mn = INF, k = -1; for (int j = 0; j < block[cur].size(); j++) { int v = block[cur][j]; if (pre[v] != -1 && dis[v] < mn) { mn = dis[v]; k = v; } } if (k != -1) { ans += dis[k]; use[pre[k]][k] = use[k][pre[k]] = 1; pre[k] = -1; for (int i = head[k]; i != -1; i = g[i].next) if (pre[g[i].v] != -1 && dis[g[i].v] > g[i].w) { dis[g[i].v] = g[i].w; pre[g[i].v] = k; } } } } void MdegreeMST() { mem(vis, 0); vis[1] = 1; cnt_block = 0, ans = 0; for (int i = 2; i <= n; i++) if (!vis[i]) { //求有多少连通分量 cnt_block++, block[cnt_block].clear(); dfs(i); } pre[1] = -1; mem(use, 0); mem(pre, 0); for (int i = 1; i <= n; i++) dis[i] = link[i] = INF; //生成最小森林 for (int i = 1; i <= cnt_block; i++) prim(i); for (int i = head[1]; i != -1; i = g[i].next) if (link[belong[g[i].v]] > g[i].w) { //将森林生成一个M度树 link[belong[g[i].v]] = g[i].w; point[belong[g[i].v]] = g[i].v; } for (int i = 1; i <= cnt_block; i++) { ans += link[i]; use[1][point[i]] = use[point[i]][1] = 1; } } void getMax(int v, int fa, int w) { //更新树上每一点到根节点的最大距离 pre[v] = fa; Edge tmp(v, fa, w); if (tmp > mx[fa]) mx[v] = tmp; else mx[v] = mx[fa]; for (int i = head[v]; i != -1; i = g[i].next) if (use[v][g[i].v] && g[i].v != fa) //不能产生环路 getMax(g[i].v, v, g[i].w); } void solve() { int degree = cnt_block; for (int i = 0; i <= n; i++) mx[i].init(); getMax(1, 0, 0); //从根节点出发更新每个点到根节点的最大边 while (degree < k) { int mn = 0, pos = 0, w; for (int i = head[1]; i != -1; i = g[i].next) if (!use[1][g[i].v] && mx[g[i].v].w - g[i].w > mn) { mn = mx[g[i].v].w - g[i].w; pos = g[i].v, w = g[i].w; } if (!pos) break; ans -= mn; degree++; use[pos][1] = use[1][pos] = 1; use[mx[pos].u][mx[pos].v] = use[mx[pos].v][mx[pos].u] = 0; getMax(pos, 1, w); } printf("Total miles driven: %d\n", ans); } void init() { mem(head, -1); cnt = 0, n = 1; char s1[55], s2[55]; scanf("%d", &m); mp.clear(); mp["Park"] = 1; int w; for (int i = 0; i < m; i++) { scanf("%s%s%d", s1, s2, &w); int u = getId(s1), v = getId(s2); addedge(u, v, w); addedge(v, u, w); } scanf("%d", &k); } int main () { init(); MdegreeMST(); solve(); return 0; }