HDU 5561 【2015合肥现场赛】 Kingdom of Tree

1 篇文章 0 订阅
0 篇文章 0 订阅

题意

给n个节点的树,边权 L[x][y] ,定义一条边 xyS 当且仅当 R[x]+R[y]L[x][y] ,现在要给每个节点x确定 R[x]0 ,使得 R[x]L[x][y][xyS] 最小。
n<=30,0<=L[x][y]<=1e9

题解

R[x]L[x][y][xyS]λ
即要求是否存在:
R[x](λL[x][y])[xyS]0
即要使:
min{R[x](λL[x][y])[xyS]}0

注意到:
R[x]+R[y]L[x][y] 前提下, R[x] 实质是最小顶标和,而树是二分图,最小顶标和转化为最大权匹配。

即枚举图,确定 [xyS] 的边,在新图中做最大权匹配,得到 minR[x]

考虑到树的最大权匹配能 O(n) 求得,直接做复杂度是 2n×n 不能过。解决方法是利用树的性质,找出质心,分隔树,易知分隔得到的子树大小 n/2 ,可行。

对每棵子树,计算 f[u][0] f[u][1] 代表 u 这个点是否被匹配情况下,u这棵子树的最大权匹配。

接下来这题最恶心的地方在于如何合并,即如何保证,或者说按照什么顺序合并能够使得保证合并得到的值,在情况合法(满足整体最大权匹配)的前提下的最小目标函数值。


(这段是错误解法,不希望被误导的读者请自行跳过。。。。)

首先想到的想法是对每个子树只记录三个值:dp0表示在所有情况下,在子树中得到的最大权匹配, dp10 表示子树根与总根相连这条边加入后,整棵子树的最大权匹配,且总根不被匹配上, dp11 表示子树根与总根相连这条边加入后,整棵子树的最大权匹配,且总根被匹配上。

接下来是合并顺序的问题。
按照w排序直接做?错,比如说
A B 3
B C 2
A D 2
每个子树选的时候,A-B是最大匹配边,而当两个连起来的时候,B-C A-D成了最大匹配边

那么按照dp11-dp0排序?也是错的。
同样是上面的例子,这时候B子树如果不匹配A-B这条边,子树中最优情况是不找匹配边,而此时A-D连起来时,A-B反而又成了匹配边。。。
这个问题引出:如果不记录子树内选边情况,单记录是否与跟这条边相连是不够的。。。

QwQ


正确的做法是:对每个 root 连出去的点 u ,计算hu,S表示 u 这棵子树,选边情况为S的情况下的最大权匹配, gu,S 表示 u 这棵子树,选边情况为S的情况下,再加入 rot -> u 这条边的情况下的最大权匹配“增量”,fu,S=hu,Sval(S)表示所求的目标函数对应在这棵树上的值。

那么观察 root ,假设它要用某个 u ,在其选边情况为S的情况下,与其匹配,那么对其它子树v都必须满足 gv,S<=gu,S ,在这个前提下,求目标函数,即为

fu,S+gu,S+v(minS{fv,Sval(rot>v)|vugv,S<=gu,S},minS{fv,S|vu})

故,我们把所有的 gu,S 排序,从小到大依次计算目标函数,
注意到这题只要找出是否存在 0 的方案,所以若存在目标函数 0 的情况则返回 True ,否则 False

在省状态之前要考虑清楚状态之间的联系,在计算目标的时候实质上是要保证两点:
1. 尽可能保证缩减状态,通常利用最优性质(即类似最优性剪枝),使目标状态分割为多个子状态的最优值,再合并
2. 要保证不能引入非法状态。

(做了一天终于搞定了QwQ)

code

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>
using namespace std;

typedef long double LD;
typedef long long LL;

const int maxn = 40;

const LL INF = 0x3f3f3f3f3f3f3f3fLL;
const LD eps = 1e-15;

int n, rt;

int tot, st[maxn];
int lk[maxn << 1], b[maxn << 1], c[maxn << 1];
bool fg[maxn << 1];
void addedge(int u, int v, int w) {
    lk[++ tot] = st[u]; b[tot] = v; c[tot] = w; fg[tot] = 0; st[u] = tot;
}

struct Node {
    int u, S;
    LD f; LL g, w;
    bool operator <(const Node& B) const {
        return g < B.g;
    }
} A[maxn * (1<<16)]; int AN;

int e[maxn], eN;

void dfsA1(int u, int fa) {
    for (int i = st[u]; i; i = lk[i]) {
        int v = b[i];
        if (v == fa) continue;
        e[eN ++] = i;
        dfsA1(v, u);
    }
}

LL f[maxn][2];
void dfsA2(int u, int fa) {
    f[u][0] = 0, f[u][1] = -INF;
    for (int i = st[u]; i; i = lk[i]) {
        int v = b[i];
        if (v == fa) continue;
        dfsA2(v, u);

        LL f0 = -INF, f1 = -INF;
        f0 = f[u][0] + max(f[v][0], f[v][1]);
        f1 = f[u][1] + max(f[v][0], f[v][1]);
        if (fg[i]) f1 = max(f1, f[u][0] + c[i] + f[v][0]);
        f[u][0] = max(f[u][0], f0), f[u][1] = max(f[u][1], f1);
    }
}

LD mu[maxn];

bool calc(LD lam) {
    AN = 0;

    for (int i = 1; i <= n; ++ i) mu[i] = 0;
    for (int i = st[rt]; i; i = lk[i]) {
        int u = b[i], w = c[i];

        eN = 0;
        dfsA1(u, rt);
        for (int S = 0; S < (1<<eN); ++ S) {
            LL sEw = 0;
            for (int i = 0; i < eN; ++ i) if (S&(1<<i)) { fg[e[i]] = fg[e[i]^1] = 1; sEw += c[e[i]]; }
            dfsA2(u, rt);
            for (int i = 0; i < eN; ++ i) if (S&(1<<i)) fg[e[i]] = fg[e[i]^1] = 0;

            LL hu = max(f[u][0], f[u][1]);
            LD fu = hu - lam * sEw;
            LL gu = max(f[u][0] + w, f[u][1]) - hu;
            A[AN ++] = (Node){u, S, fu, gu, w};

            if (S > 0 && fu < eps)
                return 1;
        }
    }

    sort(A, A+AN);

    for (int i = 0; i < AN; ++ i) {
        int u = A[i].u; LD fu = A[i].f; LL gu = A[i].g;

        if (A[i].S == 19) {
            int pp = 0;
        }

        LD s = 0;
        for (int j = 1; j <= n; ++ j) if (j != u) s += mu[j];

        if (fu + gu - A[i].w * lam + s < eps) return 1;

        mu[u] = min(mu[u], fu - A[i].w * lam);
    }

    return 0;
}

int cen, vcen;
int sz[maxn], mxChSz[maxn];
void dfsC(int u, int fa, int n) {
    sz[u] = 1; mxChSz[u] = 0;
    for (int i = st[u]; i; i = lk[i]) {
        int v = b[i];
        if (v == fa) continue;
        dfsC(v, u, n);
        sz[u] += sz[v];
        mxChSz[u] = max(mxChSz[u], sz[v]);
    }
    mxChSz[u] = max(n - sz[u], mxChSz[u]);
    if (mxChSz[u] < vcen) {
        cen = u; vcen = mxChSz[u];
    }
}
int getCen() {
    vcen = n+1;
    dfsC(1, 0, n);
    return cen;
}

void solve() {
    scanf("%d", &n);

    tot = 1; memset(st, 0, sizeof st);
    for (int i = 1; i < n; ++ i) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w); addedge(v, u, w);
//      printf("%d %d %d\n", u, v, w);
    }

    rt = getCen();

    LD L = 0, R = 1 + eps;
    for (int z = 20; z --; ) {
        LD M = (L + R) / 2;
        if (calc(M)) R = M; else L = M;
    }
    printf("%.8lf\n", (double)R);
}

int main() {
//  freopen("J.in", "r", stdin);
//  freopen("J.out", "w", stdout);

    int kase, i = 0; scanf("%d", &kase);
    for (int i = 1; i <= kase; ++ i) {
        printf("Case #%d: ", i);
        solve();
    }
//  for(;;);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值