bzoj3677 [Apio2014]连珠线

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3677

http://uoj.ac/problem/105

【题解】

我们发现这样一个结论:如果把某个点作为根,那么蓝线一定是fa-x-son这种情况。

而且一个点作为只能作为一条蓝线的中点。

那么可以dp:

令f[i][0/1]表示x是否为蓝线中点的max值。

具体解释:

1. i不是蓝线中点:那么可能从下面的所有蓝线中点连接上来取个max即可。

2. i是蓝线中点:那么我们去掉一个连接的点,把它改为连线(就是连一条son->i的蓝线),那么i就是蓝线中点了。

那么我们对每个根做一个dp,就是O(n^2)的,可以有50多分?

# include <stdio.h>
# include <string.h>
# include <algorithm>
// # include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int M = 2e5 + 10;
const int mod = 1e9+7;

# define RG register
# define ST static

int n;
int head[M], nxt[M], to[M], w[M], tot=0;
inline void add(int u, int v, int _w) {
    ++tot; nxt[tot] = head[u]; head[u] = tot;
    to[tot] = v; w[tot] = _w;
}
inline void adde(int u, int v, int _w) {
    add(u, v, _w); add(v, u, _w);
}

ll f[M][2], ans;

inline void dfs(int x, int fa) {
    f[x][0] = 0, f[x][1] = -1e18;
    for (int i=head[x]; i; i=nxt[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], x);
        f[x][0] += max(f[to[i]][0], f[to[i]][1] + w[i]);
        f[x][1] = max(f[x][1], -max(f[to[i]][0], f[to[i]][1] + w[i]) + f[to[i]][0] + w[i]);
    }
    f[x][1] += f[x][0];
}

int main() {
    scanf("%d", &n);
    for (int i=1, u, v, _w; i<n; ++i) {
        scanf("%d%d%d", &u, &v, &_w);
        adde(u, v, _w);
    }
    for (int i=1; i<=n; ++i) {
        dfs(i, 0);
        ans = max(ans, f[i][0]);
    }
    printf("%lld
", ans);
    return 0;
}
View Code

我们再记录一维,表示根是否在以i为根的子树(不包含i)的子树中。

注意到会出现这种情况:

橙色点为我们dfs下去的根(1号点),真正的根在绿色的这个点,那么红色的点在dp的时候,如果根在绿色这个地方,那么不算蓝色的中点。(可以理解为蓝线中点是按照1号点看下去来维护的)

那么我们加一维之后,其他仍然正常维护。

我们令

那么

前两个就是上面的替换一下而已。

后面的我重点解释下。。

f[i,0,1]表示i不是中点,根在i的子树(不包括i)

我们枚举真的根在的子树j,那么假设有这么一条蓝线son[j]->j->i,那么就可以推出max的前半部分:

f[j,1,1](j目前是中点)+w[i->j](蓝线)+(f[i,0,0] - t[j])(去掉这棵子树,其他的蓝线,类比f[i,0,0])

还有一半呢?就是一个特殊情况(上面画图的那个情况)

无论j是不是根,都存在这个情况,所以对f[j,0,0],f[j,0,1]取max。我们同样不能再走这棵子树了,所以要加上(f[i,0,0] - t[j])。接着我们考虑要不要有这根蓝线:我们求出有蓝线的贡献T,如果小于0就不加,大于0就加啊(废话)

那么考虑怎么求贡献:贡献就是

(w[i->j] - t[x] + w[j->k] + f[j,0,0])这个东西。也就是说,我们有(k->i->j)这样一条线。

那么我们对于(- t[x] + w[j->k] + f[j,0,0])维护前缀、后缀max,这样就能求出去掉j的所有k的max了。

f[i,1,1]表示i是中点,根在i的子树(不包括i)

前面那个max和前面一样,然后我们也是要去掉这棵子树,然后连蓝线,i->j,那么i一定是中点。

然后就这么dp。。。

# include <stdio.h>
# include <string.h>
# include <algorithm>
// # include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int M = 4e5 + 10;
const int mod = 1e9+7;

# define RG register
# define ST static

int n;
int head[M], nxt[M], to[M], w[M], tot=0;
inline void add(int u, int v, int _w) {
    ++tot; nxt[tot] = head[u]; head[u] = tot;
    to[tot] = v; w[tot] = _w;
}
inline void adde(int u, int v, int _w) {
    add(u, v, _w); add(v, u, _w);
}

ll f[M][2][2], ans;
ll t[M], pre[M], suf[M];
int tn; ll sum;

inline void dfs(int x, int fa) {
    for (int i=head[x]; i; i=nxt[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], x);
    }
    sum = tn = 0;
    for (int i=head[x]; i; i=nxt[i]) {
        if(to[i] == fa) continue;
        t[to[i]] = max(f[to[i]][0][0], f[to[i]][1][0] + w[i]);
        ++tn; pre[tn] = suf[tn] = f[to[i]][0][0] + w[i] - t[to[i]];
        sum = sum + t[to[i]];
    }
    pre[0] = suf[0] = pre[tn+1] = suf[tn+1] = -1e15;
    for (int i=1; i<=tn; ++i) pre[i] = max(pre[i], pre[i-1]);
    for (int i=tn; i>=1; --i) suf[i] = max(suf[i], suf[i+1]);
    f[x][0][0] = sum;
    if(tn == 0) f[x][1][0] = -1e15;
    else f[x][1][0] = f[x][0][0] + pre[tn];
    f[x][1][1] = -1e15;
    for (int i=head[x], j=0; i; i=nxt[i]) {
        if(to[i] == fa) continue;
        ++j; ll res = max(pre[j-1], suf[j+1]);
        f[x][0][1] = max(f[x][0][1], 
        // 1: 下面的是中点
                         max(f[to[i]][1][1] + w[i] + sum - t[to[i]],
        // 2: 下面的不是中点,这个点也不算是中点(由于不是fa-x-son),但是可以和其他点连接 
                         max(f[to[i]][0][0], f[to[i]][0][1]) + sum - t[to[i]] + max(res + w[i], 0ll)));
        f[x][1][1] = max(f[x][1][1], max(f[to[i]][0][0], f[to[i]][0][1]) + sum - t[to[i]] + w[i]);
    }
}

int main() {
    scanf("%d", &n);
    for (int i=1, u, v, _w; i<n; ++i) {
        scanf("%d%d%d", &u, &v, &_w);
        adde(u, v, _w);
    }
    dfs(1, 0);
    ans = max(f[1][0][0], f[1][0][1]);
    printf("%lld
", ans);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/galaxies/p/bzoj3677.html