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