您的位置:首页 > 其它

[学习笔记]DP模型·路径长度之和

2017-12-30 13:59 302 查看

一、问题

给定一棵n个节点的无根带权树,要从中选出K个不同的点A1,A2,...,AK,最小化∑K−1i=1dist(Ai,Ai+1)的值。n≤3000,k≤n。dist(u,v)为树上u到v的距离。

二、模型建立

可以把问题看作选出K个不同的点A1,A2,...,AK,然后从A1走到A2,再走到A3,…,最后走到AK的最小代价。

把原图视为有根树,然后树形DP。由于要选出K个点,所以考虑树形背包DP,定义f[u][i]表示在u的子树内选出i个点的最小代价,但是这样是没有正确性的。因为将两个子树合并时,有可能不断地在这两个子树内来回走。所以DP模型改为:

f[u][i]:u的子树内选出i个点,第1个点和第i个点都为u的最小代价,也就是在u的子树内,从u开始,最后回到u。

g[u][i]:u的子树内选出i个点,第1个点为u的最小代价,也就是在u的子树内,从u开始,最后回到u的子树内的任一节点。

h[u][i]:u的子树内选出i个点,这i个点中存在一个点为u的最小代价,也就是在u的子树内,从任意节点开始,最后回到子树内的任意节点,但必须经过点u。

容易得出最后结果为minni=1h[i][K]。

三、转移

一开始时,f[u][1]=g[u][1]=h[u][1]=0。

以下设当前枚举到了u的子节点v,f′[u][i]表示u的已经枚举过的子树的f[u][i],g′和h′同理。以下的图中,红色区域为u的已经枚举过的子树,黄色区域为v的子树。val(u,v)表示边(u,v)的权值。

f的转移

如下图,即从u开始,在红色区域走一圈后回到u,再到v,在黄色区域走一圈后又回到v,最后回到u,在这个路程中,边(u,v)被走了两次,所以转移为:

f[u][i+j]=min(f[u][i+j],f′[u][i]+f[v][j]+val(u,v)∗2)



g的转移

转移一:终点在黄色区域。如下图,从u开始,在红色区域走一圈后回到u,再到v,最后回到黄色区域的任意节点。在这个路程中,边(u,v)被走了一次,所以转移为:

g[u][i+j]=min(g[u][i+j],f′[u][i]+g[v][j]+val(u,v))



转移二:终点在红色区域。如下图,从u开始,先到v,在黄色区域里走一圈后回到v,再回到u,最后回到红色区域的任意节点。在这个路程中,边(u,v)被走了两次,所以转移为:

g[u][i+j]=min(g[u][i+j],g′[u][i]+f[v][j]+val(u,v)∗2)



h的转移

转移一:起点和终点一个在红色区域,一个在黄色区域。此时一定是从其中一个区域出发,到达u之后回到另一个区域内。在这个路程中,边(u,v)被走了一次,所以转移为:

h[u][i+j]=min(h[u][i+j],g′[u][i]+g[v][j]+val(u,v))



转移二:起点和终点都在红色区域。也就是从红色区域的任意一点出发,到达u后走到v,在黄色区域走一圈后回到v,再走到u,最后回到红色区域内的任意节点。在这个路程中,边(u,v)被走了两次,所以转移为:

h[u][i+j]=min(h[u][i+j],h′[u][i]+f[v][j]+val(u,v)∗2)



转移三:起点和终点都在黄色区域。也就是从黄色区域的任意一点出发,到达v后走到u,在红色区域走一圈后回到u,再走到v,最后回到黄色区域内的任意节点。在这个路程中,边(u,v)被走了两次,所以转移为:

h[u][i+j]=min(h[u][i+j],f′[u][i]+h[v][j]+val(u,v)∗2)



实现细节

考虑到有可能黄色区域没有被经过。

所以要首先令f[u][i]=f′[u][i],g[u][i]=g′[u][i],h[u][i]=h<
4000
span style="display: inline-block; width: 0px; height: 2.16em;">′[u][i]。

四、复杂度分析

看上去是O(n3)的,但是第二维i的上界只有u的子树大小,因此两层分别枚举两个区域的大小,相当于每一对点都只在lca处被计算了一次,所以复杂度为O(n2)。

五、代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || /*orz mx & xmk*/ c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 3005, M = 6005, INF = 0x3f3f3f3f;
int n, K, ecnt, nxt[M], adj
, go[M], f

, g

, h

, sze
,
ans = 0x3f3f3f3f, val[M], x
, y
, z
;
void chkmin(int &a, int b) {if (b < a) a = b;}
void add_edge(int u, int v, int w) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
void dfs(int u, int fu) {
int i, j; sze[u] = 1; memset(f[u], INF, sizeof(f[u]));
memset(g[u], INF, sizeof(g[u])); memset(h[u], INF, sizeof(h[u]));
f[u][1] = g[u][1] = h[u][1] = 0;
for (int e = adj[u], v; e; e = nxt[e]) {
if ((v = go[e]) == fu) continue; dfs(v, u);
for (i = 1; i <= sze[u] + sze[v]; i++) x[i] = f[u][i],
y[i] = g[u][i], z[i] = h[u][i];
for (i = 1; i <= sze[u]; i++) for (j = 1; j <= sze[v]; j++) {
chkmin(x[i + j], f[u][i] + (val[e] << 1) + f[v][j]);
chkmin(y[i + j], f[u][i] + val[e] + g[v][j]);
chkmin(y[i + j], (val[e] << 1) + f[v][j] + g[u][i]);
chkmin(z[i + j], g[u][i] + val[e] + g[v][j]);
chkmin(z[i + j], h[u][i] + (val[e] << 1) + f[v][j]);
chkmin(z[i + j], h[v][j] + (val[e] << 1) + f[u][i]);
}
sze[u] += sze[v]; for (i = 1; i <= sze[u]; i++)
f[u][i] = x[i], g[u][i] = y[i], h[u][i] = z[i];
}
if (K <= sze[u]) chkmin(ans, h[u][K]);
}
int main() {
int i, x, y, z; n = read(); K = read();
for (i = 1; i < n; i++) x = read(), y = read(), z = read(),
add_edge(x, y, z); dfs(1, 0);
cout << ans << endl;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: