CodeFordes 239 Problem E World Eater Brothers 树形dp

这道题来自CodeForces的第239场竞赛,问题涉及如何通过改变最少数量的有向边的方向,使两兄弟能在一棵有向树中各自控制至少一个国家,从而实现世界统治。输入包含一个国家数量n和n-1条有向边,目标是找到最小的边变更数,使得任何其他国家都在至少一个兄弟的控制下。解决方案涉及到树形动态规划,通过枚举边并计算反向边和正向边的最小数量来确定最优策略。

CF 239 problem E Eater Brothers

题目描述

https://codeforces.com/contest/239/problem/E

E. WorldEater Brothers
             time limit per test:2 secondsmemory limit per test 256 megabytes
inputstandard inputoutputstandard output

You must have heard of the two brothers dreaming of ruling the world. With all their previous plans failed, this time they decided to cooperate with each other in order to rule the world.

As you know there are n countries in the world. These countries are connected by n - 1 directed roads. If you don’t consider direction of the roads there is a unique path between every pair of countries in the world, passing through each road at most once.

Each of the brothers wants to establish his reign in some country, then it’s possible for him to control the countries that can be reached from his country using directed roads.

The brothers can rule the world if there exists at most two countries for brothers to choose (and establish their reign in these countries) so that any other country is under control of at least one of them. In order to make this possible they want to change the direction of minimum number of roads. Your task is to calculate this minimum number of roads.

Input
The first line of input contains an integer n (1 ≤ n ≤ 3000). Each of the next n - 1 lines contains two space-separated integers ai and bi (1 ≤ ai, bi ≤ n; ai ≠ bi) saying there is a road from country ai to country bi.

Consider that countries are numbered from 1 to n. It’s guaranteed that if you don’t consider direction of the roads there is a unique path between every pair of countries in the world, passing through each road at most once.

Output
In the only line of output print the minimum number of roads that their direction should be changed so that the brothers will be able to rule the world.

input                        input
4                             5
2 1                           2 1
3 1                           2 3
4 1                           4 3
                              4 5
output                        output
1                             0

排版不好看,不知道别人的这么写的。。

一棵有向树,改变其中一些树边的方向,可以选取出这样两个结点u1,u2,使得任意结点v,存在u1->v,或者u2->v,求最少改变几条边的方向

题目分析

如果需要修改边的方向,必然至少存在一个结点它的入度为0,或出度为0(想想就明白了),将树分为至少两个连通块。
那么我们就可以枚举每一条边(u->v),将树分为两个弱连通块,分别计算以u,v为根结点的树的反向边数量和正向边数量的较小值dp[u]、dp[v],则least_cost=min{ min{dp[u]}+min{dp[v]} }
正向边的数量可以由反向边的数量计算出,只需在遍历子树时,记录反向边与正向边的差值dif,再由反向边的数量cost,减去dif便可,然后更新dp[u]=min(cost,cost-dif) ------若为正向边多,则该弱联通块的根节点为u,若反向边多,则根节点为该弱联通块中的某一个结点

代码参考

/*
 * @Author: CHAOS_ORDER
 * @Date: 2019-10-05 12:58:27
 * @LastEditors: CHAOS_ORDER
 * @LastEditTime: 2019-10-05 16:23:27
 * @Description: World Eater Brothers https://codeforces.com/problemset/problem/239/E
 * @Status: Accepted 280ms
 */
#include <functional>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <iomanip>
#include <cstdio>
#include <string>
#include <vector>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define pdn(i) printf("%d\n", i)
#define plldn(i) printf("%lld\n", i)
#define pchstrn(i) printf("%s\n", i)
#define sdd(i, j) scanf("%d%d", &i, &j)
#define slldd(i, j) scanf("%lld%lld", &i, &j)
#define sdt(i, j, k) scanf("%d%d%d", &i, &j, &k)
#define slldt(i, j, k) scanf("%lld%lld%lld", &i, &j, &k)
#define sd(i) scanf("%d", &i)
#define pd(i) printf("%d", i)
#define slld(i) scanf("%lld", &i)
#define plld(i) printf("%lld", i)
#define schstr(i) scanf("%s", i)
#define pchstr(i) printf("%s", i)
#define For(i, begin, end) for (register int i = begin; i <= end; ++i)
#define rFor(i, begin, end) for (register int i = begin; i >= end; --i)
#define newline printf("\n")
#define pause system("pause")
#define test printf("after\n")
#define mod 1000000007
#define inf 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const ll maxn = 3e3 + 8;
const ll maxm = 1e7 + 8;

struct EDGE
{
    int u, v, w, next;
} edge[maxn << 1];

int edge_cnt = 1;
int head[maxn];
int dp[maxn];
int dif;

void init()
{
    memset(head, -1, sizeof(head));
}
void add_edge(int u, int v, int w)
{
    edge[edge_cnt] = (EDGE){u, v, w, head[u]};
    head[u] = edge_cnt++;
}

void dfs(int u, int fa, int cost)
{
    dp[u] = 0;
    for (int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        if (v == fa)
            continue;
        dfs(v, u, cost + edge[i].w);
        dp[u] += dp[v] + (edge[i].w == 1);
    }
    dif = max(dif, cost);
}
int main()
{
    int n, u, v;
    sd(n);
    if (n == 1)
    {
        pd(0);
        return 0;
    }
    init();
    while (--n)
    {
        sdd(u, v);
        add_edge(u, v, -1); //positive edge
        add_edge(v, u, 1);  //reverse edge
    }
    int ans = maxn;
    for (int i = 1; i < edge_cnt; i += 2)
    {

        int u = edge[i].u;
        int v = edge[i].v;
        dif = 0;
        int reve = 0;
        dfs(u, v, 0);
        reve = dp[u] - dif;
        dif = 0;
        dfs(v, u, 0);
        reve += dp[v] - dif;
        ans = min(ans, reve);
    }
    pd(ans);
    return 0;
}

参考:https://blog.youkuaiyun.com/zt2650693774/article/details/97438843

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值