【树形DP-树的重心】POJ 3107 Godfather

 POJ 3107 Godfather

题意:给出一个无向图(保证是树)【无根树】,求树的重心并输出

关于树的重心:该结点的子树中的最大结点数最小,则该结点是树的重心。也就是去掉重心后,剩余连通块中的最大连通块最小。

关于树的重心的几点性质:

  1. 一棵树最多有两个重心,且相邻。
  2. 把两棵树通过一条边相连,新的树的重心在原来两颗树重心的连线上。
  3. 树上所有点到某个点的距离和中,到重心的距离和是最小的。如果有两个重心,距离和相等。
  4. 一棵树添加或删除一个结点,树的重心最多至移动一条边的位置。

关于怎么求:我们以每个结点为根结点跑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;
}
发布了180 篇原创文章 · 获赞 54 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44049850/article/details/103861213