【P4383 [八省联考2018]林克卡特树】题解

· · 题解

P4383 [八省联考2018]林克卡特树

首先可以将题意化简为在树上选 k+1 个没有公共点的树链,求树链边权和最大。

写出树上背包,设 f(u, x, 0/1/2) 表示当前子树的根为 u,子树内有 x 个不相交的链,且根 u 状态为 0/1/2 时的最大边权和,0 代表 u 自己一个点占一条链,1 代表 u 接在某一儿子的链上,2 代表 u 不在链上或接在某对儿子的链上。

#### 转移方程 $$ f(u, x, 2) = \max\left\{\begin{aligned} &f(u,x-y,2)+\max_{0\leq fg\leq 2}\{f(v,y,fg)\}\\ &f(u,x-y+1,1)+\max_{0 \leq fg \leq 1}\{f(v, y, fg)\} + we(u,v)\\ &f(u,x,2) \end{aligned}\right. $$ 第一行代表 $u$ 不接到 $v$ 上,故 $v$ 的状态为什么都行,即 $0 \leq fg \leq 2$。 第二行代表 $u$ 接到 $v$ 上,之前 $u$ 必须接在另一儿子上,故之前 $u$ 的状态为 $1$。且 $v$ 的状态需允许 $u$ 能接到 $v$ 上,故 $0\leq fg \leq 1$。因为 $u$ 接到 $v$ 时链的总数减一,故第二行第一项第二个参数为 $x-y+1$ 而不是 $x-y$。 $$ f(u, x, 1) = \max\left\{\begin{aligned} &f(u,x-y,1)+\max_{0\leq fg\leq 2}\{f(v,y,fg)\}\\ &f(u,x-y+1,0)+\max_{0 \leq fg \leq 1}\{f(v, y, fg)\} + we(u,v)\\ &f(u,x,1) \end{aligned}\right. $$ 第一行代表 $u$ 不接到 $v$ 上,故 $v$ 的状态为什么都行,即 $0 \leq fg \leq 2$。 第二行代表 $u$ 接到 $v$ 上,之前 $u$ 必须自己一个点占一条链,故之前 $u$ 的状态为 $0$。且 $v$ 的状态需允许 $u$ 能接到 $v$ 上,故 $0\leq fg \leq 1$。因为 $u$ 接到 $v$ 时链的总数减一,故第二行第一项第二个参数为 $x-y+1$ 而不是 $x-y$。 $$ f(u, x, 0) = \max\left\{\begin{aligned} &f(u,x-y,0)+\max_{0\leq fg\leq 2}\{f(v,y,fg)\}\\ &f(u,x,0) \end{aligned}\right. $$ $u$ 自己一个点占一条链,所以 $u$ 只能不接在 $v$ 上,且之前 $u$ 的状态也是自己一个点占一条链,故 $v$ 的状态任意。 #### 边界条件 $f(u,1,0) = f(u,0,2) = f(u,1,2) =0 f(u,0,1) = f(u,1,1) = f(u, 0, 0) = -\infty

这个树上背包的复杂度是 O(nk),能拿到 60pts。

然后不会了。

题解说是 \text{wqs} 二分。

这个问题抽象一下就是限定物品数量的背包问题,即给定物品数量 k,求恰好选 k 个物品的最大权值。

直接用背包 \text{dp} 复杂度怎么也不能低于 O(nk)。

不妨设 h(x) 表示恰好选 x 物品时的的答案,若 h(x) 是凸函数,即 h^{\prime}(x) 单调(离散函数的导数可以理解成差分),则可以用 \text{wqs} 二分将时间复杂度优化至 n\log w,其中 w 为 h^{\prime}(x) 的绝对值的最大值。

$\text{wqs}$ 二分中二分的是斜率。 先假设 $h(x)$ 是上凸包,先做个观察,用不同斜率(斜率可正可负)的斜线去求与上凸包的切点,可以发现当斜率单调增加时,切点横坐标会单调下降,即切点横坐标是关于斜率的单调函数。这说明了可以通过二分斜率找到那个横坐标等于 $k$ 的斜率。 二分斜率后问题转化为求出该斜线与上凸包的切点。 设当前斜率为 $m$,斜线由一次函数 $y=m\cdot x+b$ 确定。 由凸包性质知,该斜线与上凸包相切当且仅当 $b$ 取到使得方程 $mx+b=h(x)$ 有解的最大值。 所以 $\max\{b\} = \max\{h(x)-m\cdot x\}$。 于是我们可以去掉物品数量的限制,视新的背包问题中每个物品的价值为原先的价值减去 $m$,新的背包的最大价值和就是该斜率斜线的截距 $b$,使得该新背包价值和最大的物品数量 $x_0$ 就是切点的横坐标,切点的纵坐标就是 $b+m\cdot x_0$。 得到切点坐标 $(x_0, b+m\cdot x_0)$ 后,将 $x_0$ 与 $k$ 比对,若 $x_0$ 大了,说明斜率过低,该调高斜率,若 $x_0$ 小了,说明斜率过高,该调低斜率。 到这里一切都很顺利,不过有一个 $\text{bug}$。 因为切点横坐标是关于斜率的离散函数(斜率是 $h(x)$ 的差分,是离散的),可能二分的时候根本找不到使得横坐标为 $k$ 斜率。 实际上这对我们求答案影响不大,只需做些小改动。 有两种方案,一种是在求物品数量无限制的背包时要求在价值和最大时选取的物品数量尽可能多,然后在二分斜率时找到第一个横坐标大于等于 $k$ 的切点作为答案。另一种对称,求物品数量无限制的背包时要求在价值和最大时选取的物品数量尽可能少,然后在二分斜率时找到第一个横坐标小于等于 $k$ 的切点作为答案。 还有一个问题,就是不同斜率对应的切点横坐标可能相同,不过这不会影响答案。 回到原题。 链相当于是物品,对链数量的限制是 $k+1$,二分斜率 $m$ 后每条链都要减去 $m$。 新的背包对物品数量不限制,较上边背包 $\text{dp}$ 的转移式简单,但同时也要新开一个数组记录最大价值和的物品数量。 设 $f(u,0/1/2)$ 表示当前子树的根为 $u$,且根 $u$ 状态为 $0/1/2$ 时的最大边权和,$g(u,0/1/2)$ 表示 $f(u,0/1/2)$ 对应的物品数量。 #### 转移方程 $$ f(u,2) = \max\left\{\begin{aligned} &f(u,2)+\max_{0\leq fg\leq 2}\{f(v,fg)\}\\ &f(u,1)+\max_{0 \leq fg \leq 1}\{f(v, fg)\} + we(u,v)\\ &f(u,2) \end{aligned}\right.\\ g(u,2) = \left\{\begin{aligned} &g(u,2)+g(v,fg)\\ &g(u,1)+g(v,fg)-1\\ &g(u,2) \end{aligned}\right. $$ $g$ 的转移取决于 $f$ 的转移。 $$ f(u, 1) = \max\left\{\begin{aligned} &f(u,1)+\max_{0\leq fg\leq 2}\{f(v,fg)\}\\ &f(u,0)+\max_{0 \leq fg \leq 1}\{f(v,fg)\} + we(u,v)\\ &f(u,1) \end{aligned}\right.\\ g(u,1) = \left\{\begin{aligned} &g(u,1)+g(v,fg)\\ &g(u,0)+g(v,fg)-1\\ &g(u,1) \end{aligned}\right. $$ $$ f(u,0) = \max\left\{\begin{aligned} &f(u,0)+\max_{0\leq fg\leq 2}\{f(v,fg)\}\\ &f(u,0) \end{aligned}\right.\\ g(u,0) = \left\{\begin{aligned} &g(u,0)+g(v,fg)\\ &g(u,0) \end{aligned}\right. $$ #### 边界条件 $f(u,0)=-m,\ \ f(u,1) = -\infty,\ \ f(u,2)=0 g(u,0)=1,\ \ g(u,1)=-\infty,\ \ g(u,2)=0

为了方便,代码实现中可以将 f 与 g 封装进一个二元组,然后定义二元组的比较函数。

#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <assert.h>
using namespace std;

#define re register
#define sf scanf
#define pf printf
#define nl() putchar('\n')
#define ms(x, val) memset(x, val, sizeof(x))
#define ll long long
#define _for(i, a, b) for(re int (i) = (a); (i) < (b); ++(i))
#define _rfor(i, a, b) for(re int (i) = (a); (i) <= (b); ++(i))
#define _fev(p, u) for(re int (p) = head[(u)]; (p); (p) = nex[(p)])
#define inf 0x7ffffffffffffffll
#define ninf (-inf)
#define maxn 300005
#define maxk 105

template <class T>
void print(string name, T arr[], int n, int flag = 1){
    cout<<name<<":";
    _for(i, 0, n)cout<<" "<<arr[i+flag];
    cout<<endl;
}

int rdnt(){
    re int x = 0, sign = 1;
    re char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') sign = -1; c = getchar(); }
    while (c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (c ^ 48), c = getchar();
    return x * sign;
}

int ecnt = 1, k,
    head[maxn],
    to[maxn*2],
    nex[maxn*2],
    we[maxn*2];
ll  mi;
struct Par{
    ll f; int g;
    inline bool operator<(const Par &t)const{ return (f == t.f) ? (g < t.g) : (f < t.f); }
} par[maxn][3];
#define f(u, fg) par[u][fg].f
#define g(u, fg) par[u][fg].g

void add_edge(re int u, re int v, re int w){
    to[++ecnt] = v; nex[ecnt] = head[u]; head[u] = ecnt; we[ecnt] = w;
    to[++ecnt] = u; nex[ecnt] = head[v]; head[v] = ecnt; we[ecnt] = w;
}

#define sum(x, y, z) ((x == ninf || y == ninf) ? (ninf) : (x+y+z))
void ud(Par &x, Par y){ if (x < y) x = y; }

void dfs(int u, int fa){
    f(u, 0) = -mi; f(u, 2) = 0; f(u, 1) = ninf;
    g(u, 0) = 1; g(u, 2) = 0; g(u, 1) = 0;
    _fev(p, u){
        int v = to[p];
        if (v == fa) continue;
        dfs(v, u);
        Par tu1, tu2, tv1 = max(par[v][0], par[v][1]), tv2 = max(tv1, par[v][2]);

        tu1.f = sum(f(u, 1), tv1.f, we[p] + mi); tu1.g = g(u, 1) + tv1.g - 1;
        tu2.f = sum(f(u, 2), tv2.f, 0); tu2.g = g(u, 2) + tv2.g;
        ud(par[u][2], max(tu1, tu2));

        tu1.f = sum(f(u, 0), tv1.f, we[p] + mi); tu1.g = g(u, 0) + tv1.g - 1;
        tu2.f = sum(f(u, 1), tv2.f, 0); tu2.g = g(u, 1) + tv2.g;
        ud(par[u][1], max(tu1, tu2));

        tu1.f = sum(f(u, 0), tv2.f, 0); tu1.g = g(u, 0) + tv2.g;
        ud(par[u][0], tu1);

    }
}

int main(){
    #ifndef ONLINE_JUDGE
    freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    #endif

    re int n = rdnt(); k = rdnt()+1;
    re ll l, r = 0, as = ninf;
    _rfor(i, 1, n-1){
        re int u = rdnt(), v = rdnt(), w = rdnt();
        if (w > 0) r += w; 
        add_edge(u, v, w);
    }
    l = -r; ++r;
    while(l < r){
        mi = (l+r)>>1;
        dfs(1, 0);
        Par ans = max(par[1][0], par[1][1]);
        ans = max(ans, par[1][2]);
        //pf("mi:%lld af:%lld ag:%d\n", mi, ans.f + ans.g*mi, ans.g);
        if (ans.g >= k) as = ans.f + ans.g*mi, l = mi+1;
        else r = mi;

    }
    pf("%lld\n", as);


    return 0;
}