[CF 219D]Choosing Capital for Treeland[树形DP]

题意:

给出n个点, n-1条有向边, 问从一个点出发到其他所有点时, 使得逆的边数最小的出发点, 以及逆的边数. 有多个出发点的话升序输出.

思路:

树形DP.

这里DP的要义是一种递推, 只不过是沿着树的结构去递推.

有一个关系需要发现: 当知道一个点到其他所有点需要逆的边的条数之后, 其他各点的结果可以通过递推求出:

假设root有son1和son2, root到其他所有点, 都是先到son1, son2, 再接着往下走.

那么求son1到所有点的时候, son1到son1子树的计数情况和root的计数情况相同, son1到root之后再走son2子树的计数情况也和root相同. 于是只有son1到root之间一段不同.

如果是root->son1, 那么dp[son1] = dp[root] + 1; 否则dp[son1] = dp[root] - 1;

再dfs一遍即可.

#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 200005;
struct pool
{
    int v,w,next;
}g[MAXN<<1];
int head[MAXN],num,n;
int dp[MAXN],rt;
bool vis[MAXN];

void add(int u, int v)
{
    g[++num].v = v;
    g[num].w = 0;
    g[num].next = head[u];
    head[u] = num;
    g[++num].v = u;
    g[num].w = 1;
    g[num].next = head[v];
    head[v] = num;
}

void dfs1(int u)
{
    vis[u] = true;
    for(int i=head[u],v;i,v=g[i].v;i=g[i].next)
    {
        if(!vis[v])
        {
            rt += g[i].w;
            dfs1(v);
        }
    }
}

void dfs2(int u)
{
    vis[u] = true;
    for(int i=head[u],v;i,v=g[i].v;i=g[i].next)
    {
        if(!vis[v])
        {
            if(g[i].w)  dp[v] = dp[u] - 1;
            else dp[v] = dp[u] + 1;
            if(dp[v]<rt)    rt = dp[v],num = 1;
            else if(dp[v]==rt)  num++;
            dfs2(v);
        }
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        add(u,v);
    }
    dfs1(1);
    dp[1] = rt;
    memset(vis,false,sizeof(vis));
    num = 1;
    dfs2(1);
    printf("%d\n",rt);
    for(int i=1,cnt=0;i<=n;i++)
    {
        if(dp[i]==rt)   printf("%d%c",i,(++cnt==num)?'\n':' ');
    }
}


你可能感兴趣的:([CF 219D]Choosing Capital for Treeland[树形DP])