Codeforces #322 div2. F Zublicanes and Mumocrates 树形dp

题目

题目链接:http://codeforces.com/contest/581/problem/F

题目来源:CF#322最后一道,前四道很容易,这道是第二难的。

简要题意:给定一棵树,点有两种颜色,度数为 1 的点一半黑一半白,求连接相异颜色点的最少边数。

数据范围:2n5000

题解

首先可以从度数大于 1 的点开始dfs,这样题意中的点就都是叶子了,n=2特判。

这题我觉得很难,初步的想法基本是 dp[i][j][k] 表示 i 号点,有j个白叶子, k 表示当前点的颜色。

但是这么做之后发现状态方程非常难推。

之后去看各种神犇的代码,然后发现这题可以很巧妙地解决。

首先需要一个结论,即将子树的所有节点颜色反转后代价不变

有了这个结论,那知道子节点为白色的代价可以直接得到白色节点数取补后子节点黑色的最小代价。

此处可以使用反证法,如果有更小的,反转回来白色的就不是最小代价了。

考虑当前节点有cnt[x]个叶子节点, dp[i][j] 表示 i 点为白色,有j个黑色叶子的最小代价。

子节点为白色的话可以直接通过两个循环推出。

子节点为黑色的话可以通过 dp[i][cnt[i]j] 来推出,由于当前是白色,要 +1

至此我们就能够实现这个奇妙的dp了,答案为 dp[st][cnt[st]/2]

实现

注意要从度数大于 1 的点开始dfs,然后n=2没有这样的点,要特判。

dp[i][cnt[i]j] 推出黑色的话是摆在两层循环里,但我也看到过有一份代码是摆在外面的,我不确定这么做是否真的会有问题。

我觉得可能是会有问题的,因为这相当于把数组的意义变为了上面连接一个白色的最小代价,在起始点这个位置这个东西其实应该是不对的。

但是实际上我发现下面的写法也能过,也就是说应该是只有后半段被前半段更新的,由于结果是在前半段就不会被更新了。但是仍然不知道怎么去得到这个结论。

改下标后:http://codeforces.com/contest/581/submission/13328490
原本神犇的代码:http://codeforces.com/contest/581/submission/13294185

for (int i = cnt[u]; i > cnt[u]/2; i--) {
    dp[u][i] = min(dp[u][i], dp[u][cnt[u] - i] + 1);
}

代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>

#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define sz(x) ((int)(x).size())
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
LL powmod(LL a,LL b, LL MOD) {LL res=1;a%=MOD;for(;b;b>>=1){if(b&1)res=res*a%MOD;a=a*a%MOD;}return res;}
// head
const int N = 5000+5;
const int INF = 0x3f3f3f3f;

struct Edge {
    int to, nxt;
    Edge(int to, int nxt) : to(to), nxt(nxt){}
    Edge() {}
};

int head[N];
Edge e[N*2];

void addEdge(int from, int to, int cnt) {
    e[cnt] = Edge(to, head[from]);
    head[from] = cnt;
}

int leaf[N];
int dp[N][N];
int temp[N];
int deg[N];

inline void setMin(int &x, int y) {
    if (y < x) x = y;
}
void dfs(int x, int p) {
    dp[x][0] = 0;
    for (int i = head[x]; ~i; i = e[i].nxt) {
        int to = e[i].to;
        if (to != p) {
            dfs(to, x);
            for (int i = 0; i <= leaf[x]+leaf[to]; i++) temp[i] = INF;
            for (int i = 0; i <= leaf[x]; i++) {
                for (int j = 0; j <= leaf[to]; j++) {
                    setMin(temp[i+j], dp[x][i]+min(dp[to][j], dp[to][leaf[to]-j]+1));
                }
            }
            leaf[x] += leaf[to];
            for (int i = 0; i <= leaf[x]; i++) dp[x][i] = temp[i];
        }
    }
    if (deg[x] == 1) leaf[x] = 1;
}
int main()
{
    memset(head, -1, sizeof head);
    memset(dp, INF, sizeof dp);

    int n, u, v, cnt = 0;
    scanf("%d", &n);
    if (n == 2) return puts("1");

    for (int i = 1; i < n; i++) {
        scanf("%d%d", &u, &v);
        u--, v--;
        addEdge(u, v, cnt++);
        addEdge(v, u, cnt++);
        deg[u]++, deg[v]++;
    }
    for (int i = 0; i < n; i++) {
        if (deg[i] > 1) {
            dfs(i, -1);
            printf("%d\n", dp[i][leaf[i]/2]);
            break;
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值