A - 氪金带东
题目
样例输入
5
1 1
2 1
3 1
1 1
样例输出
3
2
3
4
4
思路
如果从每个点暴力搜索可能会超时,需要进行n次bfs。
因此采用一个比较巧妙的方法:从任意一点起始开始进行搜索,搜索到最远的一个节点x,然后再从x搜索距离x节点最远的节点y(可以不准确的理解为寻找圆上两点,两点连线为过任意点的直径)。那么每一个节点到其他节点的最远距离就是x节点或y节点到达该点较大的距离。
具体为:
·从1号节点进行bfs找到最远节点x。
·从x进行bfs,找到最远节点y,同时标记到每一个节点的距离。
·从y进行bfs,标记到每一个节点的距离。
·输出第2次bfs与第3次bfs中标记的最大距离。
通过这个方法,成功将bfs次数降为3次。
图的存储方式是链式前向星(截图来源于程设课课件
代码
#include<iostream>
#include<queue>
#include<algorithm>
#define MAXN 10010
using namespace std;
int vis1[MAXN], vis2[MAXN], vis3[MAXN], dis1[MAXN], dis2[MAXN], dis3[MAXN];
struct Edge
{
int u, v, w, next;
}Edge[2 * MAXN];
int head[MAXN], total;
void init()
{
total = 1;
for (int i = 0; i < MAXN; i++)
head[i] = -1;
}
void addEdge(int uu, int vv, int ww)
{
Edge[total].u = uu;
Edge[total].v = vv;
Edge[total].w = ww;
Edge[total].next = head[uu];
head[uu] = total;
total++;
}
void begin()
{
for (int i = 0; i < MAXN; i++)
{
vis1[i] = vis2[i] = vis3[i] = -1;
dis1[i] = dis2[i] = dis3[i] = 0;
}
}
int bfs(int x, int vis[], int dis[])
{
queue<int> q;
q.push(x);
vis[x] = 1;
int max = 0;
int maxDis = 0;
while (!q.empty())
{
int a = q.front();
q.pop();
int nextt = head[a];
while (nextt != -1)
{
int b = Edge[nextt].v;
if (vis[b] == -1)//没访问过
{
q.push(b);
vis[b] = 1;
dis[b] = dis[a] + Edge[nextt].w;
if (dis[b] > maxDis)
{
maxDis = dis[b];
max = b;
}
}
nextt = Edge[nextt].next;
}
}
return max;
}
int main()
{
int n;
while (cin >> n)
{
begin();
init();
for (int i = 2; i <= n; i++)
{
int v, w;
cin >> v >> w;
addEdge(i, v, w);
addEdge(v, i, w);
}
int a = bfs(1, vis1, dis3);
int b = bfs(a, vis2, dis1);
int c = bfs(b, vis3, dis2);
for (int i = 1; i <= n; i++)
cout << max(dis1[i], dis2[i]) << endl;
}
//system("Pause");
}
B - 戴好口罩!
题目
思路
这道题目比较简单,可以采用并查集解决。
并查集要记录每个集合的元素数目。只需要将每个小团体中的每个人所在的集合合并,最后找到0号同学的代表元素,输出其所在集合的人数即可。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#define MAXN 30010
using namespace std;
int an[MAXN];
int rankk[MAXN];
void init()
{
for (int i = 0; i < MAXN; i++)
{
an[i] = i;
rankk[i] = 1;
}
}
int find(int x)
{
if (an[x] == x) return x;
else return an[x] = find(an[x]);
}
bool unite(int x, int y)
{
x = find(x);
y = find(y);
//cout << "x:" << x << " y:" << y << endl;
if (x == y) return false;
if (rankk[x] > rankk[y])
swap(x, y);
an[x] = y;
rankk[y] += rankk[x];
rankk[x] = rankk[y];
return true;
}
int main()
{
int n, m, num, x, y;
while (cin >> n >> m)
{
memset(rankk, 0, sizeof rankk);
if (n > 0) init();
else break;
for (int i = 0; i < m; i++)
{
cin >> num >> x;
for (int j = 1; j < num; j++)
{
cin >> y;
unite(x, y);
x = y;
//cout << "sum:" << rankk[0] << endl;
}
}
int z = find(0);
cout << rankk[z] << endl;
}
}
C - 掌握魔法の东东 I
题目
样例输入
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
样例输出
9
思路
如果这道题目没有“黄河之水天上来”,只需要计算如何连通各个田的话,就是一个简单的最小生成树问题,可以使用kruskal算法解决,但是这道题还要考虑每个点如何“黄河之水天上来”,题目就变得复杂了。
解决方法为:将“黄河之水”视为一个特殊节点0号节点,它到各个田的权重即为那个田“黄河之水天上来”的消耗。
因为至少有一次“黄河之水天上来”,所以可以先将所有“黄河之水”的消耗进行排序,选出消耗最小的进行“黄河之水”,然后再将“黄河之水”与饮水一起排序,进行kruskal算法。
kruskal算法为:
在图中找到一条权重最小的边,如果加入这条边不会构成环路,就选择它,否则抛弃它。
重复该过程,直到形成最小生成树。
如果判断了所有边而无法生成最小生成树,则算法失败,说明原图不连通。
注意
因为必须“黄河之水”,所以在kruskal算法开始之前需要先选择出“黄河之水”的田,再进行kruskal算法,并且需要将该边从kruskal算法选择边的范围中删除。
代码
#include<iostream>
#include<queue>
#include<algorithm>
#define MAXN 100010
using namespace std;
int vis1[MAXN], vis2[MAXN], vis3[MAXN], dis1[MAXN], dis2[MAXN], dis3[MAXN], an[MAXN], rankk[MAXN];
void init()
{
for (int i = 0; i < MAXN; i++)
{
an[i] = i;
rankk[i] = 1;
}
}
int find(int x)
{
if (an[x] == x) return x;
else return an[x] = find(an[x]);
}
bool unite(int x, int y)
{
x = find(x);
y = find(y);
//cout << "x:" << x << " y:" << y << endl;
if (x == y) return false;
if (rankk[x] > rankk[y])
swap(x, y);
an[x] = y;
rankk[y] += rankk[x];
rankk[x] = rankk[y];
return true;
}
struct Edge
{
int u, v, w, next;
}Edge[2 * MAXN], e[MAXN];
bool cmp(struct Edge &a, struct Edge &b)
{
return a.w < b.w;
}
int head[MAXN];
int total = 1, tot = 0;
void init2()
{
total = 1;
tot = 0;
for (int i = 0; i < MAXN; i++)
head[i] = -1;
}
void insert(int uu, int vv, int ww)
{
e[tot].u = uu;
e[tot].v = vv;
e[tot].w = ww;
tot++;
}
void addEdge(int uu, int vv, int ww)
{
Edge[total].u = uu;
Edge[total].v = vv;
Edge[total].w = ww;
Edge[total].next = head[uu];
head[uu] = total;
total++;
}
void begin()
{
for (int i = 0; i < MAXN; i++)
{
vis1[i] = vis2[i] = vis3[i] = -1;
dis1[i] = dis2[i] = dis3[i] = 0;
}
}
int kruskal(int n, int begin)
{
int sum = begin, num = 1;
for (int i = 1; i <= (n * (n - 1) / 2 + n - 1) && num < n; i++)
{
if (unite(e[i].u, e[i].v))
{
//cout << "w:" << e[i].w << endl;
sum += e[i].w;
num++;
}
}
return sum;
}
int main()
{
int n;
cin >> n;
int w;
init();
init2();
for (int i = 1; i <= n; i++)
{
cin >> w;
addEdge(0, i, w);
addEdge(i, 0, w);
insert(0, i, w);
}
std::sort(e, e + n, cmp);
unite(e[0].u, e[0].v);
int b = e[0].w;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
cin >> w;
if (i < j)
{
addEdge(i, j, w);
addEdge(j, i, w);
insert(i, j, w);
}
}
std::sort(e + 1, e + n * (n - 1) / 2 + n, cmp);
//cout << "b:"<<b << endl;
std::cout << kruskal(n, b) << endl;
std::system("Pause");
}
D - 数据中心
题目
思路
反复读完题目之后,我终于明白了这个题目在求一颗生成树,使它的最大边最小。
(因为是需要在树的每一层中求出一个最大边,为层的最大边,再在层的最大边中求一个最大边,即为整颗生成树的最大边)
那么直接使用kruskal算法求最小生成树,然后输出最大边即可(root的条件甚至都不需要)。
题目本身并不复杂,就是描述的方式不易理解。
代码
#include<iostream>
#include<queue>
#include<algorithm>
#define MAXN 100010
using namespace std;
int an[MAXN], rankk[MAXN];
void init()
{
for (int i = 0; i < MAXN; i++)
{
an[i] = i;
rankk[i] = 1;
}
}
int find(int x)
{
if (an[x] == x) return x;
else return an[x] = find(an[x]);
}
bool unite(int x, int y)
{
x = find(x);
y = find(y);
//cout << "x:" << x << " y:" << y << endl;
if (x == y) return false;
if (rankk[x] > rankk[y])
swap(x, y);
an[x] = y;
rankk[y] += rankk[x];
rankk[x] = rankk[y];
return true;
}
struct Edge
{
int u, v, w, next;
}Edge[2 * MAXN], e[MAXN];
bool cmp(struct Edge &a, struct Edge &b)
{
return a.w < b.w;
}
int head[MAXN];
int total = 1, tot = 0;
void init2()
{
total = 1;
tot = 0;
for (int i = 0; i < MAXN; i++)
head[i] = -1;
}
void insert(int uu, int vv, int ww)
{
e[tot].u = uu;
e[tot].v = vv;
e[tot].w = ww;
tot++;
}
void addEdge(int uu, int vv, int ww)
{
Edge[total].u = uu;
Edge[total].v = vv;
Edge[total].w = ww;
Edge[total].next = head[uu];
head[uu] = total;
total++;
}
int kruskal(int n, int m)
{
int max = 0, num = 0;
for (int i = 0; i <= m && num < n - 1; i++)
{
if (unite(e[i].u, e[i].v))
{
//cout << "w:" << e[i].w << endl;
num++;
if (num == n - 1) max = e[i].w;
}
}
return max;
}
int main()
{
int n, m, r;
cin >> n >> m >> r;
int x, y, w;
init();
init2();
for (int i = 0; i < m; i++)
{
cin >> x >> y >> w;
addEdge(x, y, w);
addEdge(y, x, w);
insert(x, y, w);
}
std::sort(e, e + m, cmp);
//cout << "b:"<<b << endl;
std::cout << kruskal(n, m) << endl;
std::system("Pause");
}