POJ 3107 Godfather
题意:给出一个无向图(保证是树)【无根树】,求树的重心并输出
关于树的重心:该结点的子树中的最大结点数最小,则该结点是树的重心。也就是去掉重心后,剩余连通块中的最大连通块最小。
关于树的重心的几点性质:
- 一棵树最多有两个重心,且相邻。
- 把两棵树通过一条边相连,新的树的重心在原来两颗树重心的连线上。
- 树上所有点到某个点的距离和中,到重心的距离和是最小的。如果有两个重心,距离和相等。
- 一棵树添加或删除一个结点,树的重心最多至移动一条边的位置。
关于怎么求:我们以每个结点为根结点跑dfs,然后记录去掉该结点后其子树的最大结点数。
我们任选一个结点作为根结点,将无根树转为有根树,跑dfs。我们需要维护的是该结点作为根结点所在子树上的结点总数,以及该结点作为根结点,它所有子树的最大子树的结点数。【所谓最大子树这里指的是结点数最多】
即num[ i ]: i 作为根结点它所在子树上的结点总数
即max_son[ i ]: i 作为根结点他所有子树中最大子树的结点数。
之所以维护这两个信息是为了我们要根据儿子推父亲的信息。这里没有记忆化,因为没有重复计算。
我们选择A为根结点转换为上图的“有根树”。
图中的结点B,B作为根结点,它实际上有三个子树,但是我们不必往上跑它“父亲”所在子树的结点个数,因为总结点数减去B在上图的“有根树”中的子树的总结点数就是它“父亲”所在子树结点个数。所以我们只需要遍历它的“子树”D和E即可。这也说明了我们避免了重复计算,以及不需要记忆化。
另外:这道题卡了vector,所以用链式前向星存图
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <limits>
#include <set>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 5e4 + 5;
int head[maxN], cnt, vis[maxN];
struct EDGE{
int adj, to;
EDGE(int a = -1, int b = 0): adj(a), to(b) {}
}edge[maxN << 1];
int n, dp[maxN], MIN;
void init()
{
memset(dp, -1, sizeof(dp));
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
cnt = 0;
}
void add_edge(int u, int v)
{
edge[cnt] = EDGE(head[u], v);
head[u] = cnt ++ ;
}
struct node{
int node_num;//结点数
int siz;//最大子树的结点数
node(int a = 0, int b = 0) : node_num(a), siz(b) {}
};
node dfs(int now)
{
node rt = node(1, 0);
vis[now] = 1;
for(int i = head[now]; ~i; i = edge[i].adj)//i是标号
{
if(vis[edge[i].to])
continue;
node son = dfs(edge[i].to);
rt.siz = max(rt.siz, son.node_num);//父亲结点的子树中最大的结点数
rt.node_num += son.node_num;//父亲结点的结点数等于自身加上其儿子结点的结点数
}
rt.siz = max(rt.siz, n - rt.node_num);
dp[now] = max(dp[now], rt.siz);//dp[]存的是以该结点为真正根结点,所有子树中的最大结点数
if(MIN > dp[now])
MIN = dp[now];
return rt;
}
int main()
{
scanf("%d", &n);
init();
for(int i = 0; i < n - 1; i ++ )
{
int u, v;
scanf("%d%d", &u, &v);
add_edge(u, v);
add_edge(v, u);
}
MIN = INF;
node rt = dfs(1);
for(int i = 1; i <= n; i ++ )
{
if(dp[i] == MIN)
printf("%d ", i);
}
putchar('\n');
return 0;
}
今天又重新敲了一遍,感觉还是下边这种写法比较普遍一些。也感觉比上边的代码更加清晰明了。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <limits>
#include <set>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 5e4 + 5;
int n;
int head[maxN], cnt;
struct EDGE{
int adj, to;
EDGE(int a = -1, int b = 0): adj(a), to(b) {}
}edge[maxN << 1];
void add_edge(int u, int v)
{
edge[cnt] = EDGE(head[u], v);
head[u] = cnt ++;
}
int num[maxN];//num[i]:"有根树中"以i为根的子树上的所有结点数
int max_son[maxN];//max_son[i]:以i为真正根的所有子树中最大子树的结点数
int core;
set<int>st;
void dfs(int now, int fa)
{
num[now] = 1;
for(int i = head[now]; ~i; i = edge[i].adj)
{
if(edge[i].to == fa)
continue;
dfs(edge[i].to, now);
num[now] += num[edge[i].to];
max_son[now] = max(max_son[now], num[edge[i].to]);
}
max_son[now] = max(max_son[now], n - num[now]);
if(max_son[core] > max_son[now])
{
core = now;
st.clear();
st.insert(now);
}
else if(max_son[core] == max_son[now])
st.insert(now);
return;
}
void init()
{
memset(head, -1, sizeof(head));
cnt = 0;
memset(max_son, 0, sizeof(max_son));
max_son[0] = INF;
core = 0;
}
int main()
{
scanf("%d", &n);
init();
for(int i = 0; i < n - 1; i ++ )
{
int u, v;
scanf("%d%d", &u, &v);
add_edge(u, v);
add_edge(v, u);
}
dfs(1, 0);
while(!st.empty())
{
int ans = *st.begin();
printf("%d ", ans);
st.erase(ans);
}
putchar('\n');
return 0;
}