题意:
给出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':' ');
}
}