题意:
给定n个点的有向树,下面n-1行给出正向的边。
问:修改尽可能小的边的方向,可以任选2个起点使得这两个点bfs能遍历完所有点。
思路:枚举每一条边,把这条边断开,把树分成两部分,每次计算这两部分的最小花费。
这样我们每次只要计算一个子树上所需要改动的最小边数。
对于一棵树所需要修改的最小边方向的方法:
先求出表示以i为根的子树,用i作为起点的花费,即dp[i];
但是在这个子树中,以 i 为起点并不一定是最优解,所以我们要去寻找这个最优的起点 u ;
树种的任意起点 u 遍历整棵树所需要改变的边数不用重新去遍历每条边,只要看看它和 i 这个点之间的那段路需要改变多少边数即可。
即:cost[u] = dp[root] - ((u->root方向的边数)- (root->u方向的边数))
为了得到最小的cost[u],就必需使 ((u->root方向的边数)-(root->u方向的边数)) 最大
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 10000001
const int maxn=1e5+10;
int head[maxn];
int dp[maxn];
int n,u,v,edgenum,arr;
struct Edge
{
int from, to, dis, next;
}edge[maxn<<1];
void add(int u, int v, int d)
{
Edge E={u,v,d,head[u]};
edge[edgenum]=E;
head[u]=edgenum++;
}
void dfs(int u, int fa, int val)//val记录每个子节点回到根需要改变的边数
{
dp[u]=0;
for(int i=head[u]; ~i; i=edge[i].next)//~i:i!=-1
{
int v=edge[i].to;
if(v==fa) continue;
dfs(v, u, val-edge[i].dis);
dp[u]+=dp[v]+(edge[i].dis!=1); //从u点出发,遍历它的子节点需要改变的根数
}
arr= max(arr, val); // 寻找每个子节点到根节点能够少改变的边数的最大值
//就是在求边断开后 ,这个子树里从最优的那个点出发需要改变边数=dp[u]-arr;
}
int main()
{
while(scanf("%d", &n)!=EOF)
{
if(n==1)
{
puts("0"); continue;
}
memset(head,-1,sizeof(head));
edgenum=0; //记录边的数量
for(int i=1; i<n; i++)
{
scanf("%d%d", &u, &v);
add(u,v,1);
add(v,u,-1);
}
int ans=inf;
for(int i=0; i<edgenum; i+=2)
{
u=edge[i].from;
v=edge[i].to;
arr=-inf;
dfs(u,v,0);
int tmp=dp[u]-arr;//断开后第一个子树的最优解
arr=-inf;
dfs(v,u,0);
tmp+=dp[v]-arr;//加上第二个子树的最优解
ans=min(ans, tmp); //遍历所有的边以后求得的最优解
}
printf("%d\n", ans);
}
return 0;
}
~