【题解】[NOI2006] 网络收费
DP 神题。
首先观察表格,发现异色的点对无论如何都会产生 \(f\) 的贡献,同色的点对当且仅当该颜色不是它们 \(\rm LCA\) 管辖子树中出现次数最多的颜色时,才会产生 \(2f\) 的贡献。
由于贡献只有 \(0,f,2f\) 的区别,所以我们不难想到,如果我们固定一个路由器的颜色为 \(c\),那么对于子树中所有叶子,如果颜色和 \(c\) 相同,则不产生贡献,否则产生 \(\frac{size}{2}\times f\) 的贡献,其中 \(size\) 为子树中的叶子总数。
所以一个叶子的贡献只与它到根路径上所有点的颜色有关,路由器的颜色指子树中出现次数最多的颜色。
试着动态规划,设计状态 \(f[i][j]\) 表示子树 \(i\) 中有 \(j\) 个 \(1\) 的最优值,但是这玩意转移后根本不优,因为子树中某个点可能当前没有贡献,但是在某个祖先节点产生大量贡献。并且我们不知道这 \(j\) 个节点的流量之和。
做到这里就自闭了,翻了下题解发现做法太妙了。
传统的树形 DP 都是以子树为状态,本题是完全二叉树,所以深度很小,所以我们可以对当前节点到根的路径进行状态压缩。定义 \(f[i][j][k]\) 表示节点 \(i\) 的子树中,选了 \(j\) 个 \(1\),且 \(i\) 到根节点的状态为 \(k\) 的最优值。
我们发现深度每增加 \(1\) ,\(k\) 的规模 \(\times 2\),而 \(j\) 的规模 \(/2\),所以 \(j \times k\) 的规模始终是 \(\mathcal{O}(N)\) 级别的。所以直接 DP 时间复杂度是 \(\mathcal{O}(N^2\log N)\) 的,空间是 \(\mathcal{O}(N^3)\) ,将 \(j\) 和 \(k\) 压缩到一起空间就 \(\mathcal{O}(N^2)\) 了,可以通过本题。
还有更好写的方法,直接将 \(k\) 作为参数在递归时传递即可,这样也可以保证不重复计算,时间复杂度时 \(\mathcal{O}(N^2\log N)\),空间复杂度 \(\mathcal{O}(N^2)\) 。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 1030
using namespace std;
int n,c[N],w[N],u[N],f[N << 1][N],s[N][N];
#define ls (x<<1)
#define rs (ls|1)
void solve(int x,int l,int r,int dep,int S){
if(l == r){
f[x][u[l]] = 0;
f[x][u[l] ^ 1] = c[l];
int L = l,R = r,len = 1,y = x;
rep(i, 0, dep - 1){
int op = 1 ^ (1 & S);
if(y & 1)f[x][op] += s[l][L - 1] - s[l][L - len - 1], L -= len;
else f[x][op] += s[l][R + len] - s[l][R], R += len;
len <<= 1, y >>= 1;S >>= 1;
}
return ;
}
int mid = (l + r) >> 1,len = r - mid;
rep(i, 0, r - l + 1)f[x][i] = 0x3f3f3f3f;
solve(ls, l, mid, dep + 1 , S << 1);
solve(rs, mid + 1, r, dep + 1, S << 1);
rep(p, 0, len)rep(q, 0, len - p)
f[x][p + q] = min(f[x][p + q], f[ls][p] + f[rs][q]);
solve(ls, l, mid, dep + 1 , 1 + (S << 1));
solve(rs, mid + 1, r, dep + 1, 1 + (S << 1));
rep(p, 0, len)rep(q, len + 1 - p, len)
f[x][p + q] = min(f[x][p + q], f[ls][p] + f[rs][q]);
}
int main(){
scanf("%d",&n);
n = 1 << n;
rep(i, 1, n)scanf("%d",&u[i]);
rep(i, 1, n)scanf("%d",&c[i]);
rep(i, 1, n)rep(j, i + 1, n){
int x;scanf("%d",&x);
s[i][j] += x;s[j][i] += x;
}
rep(i, 1, n)rep(j, 1, n)s[i][j] += s[i][j - 1];
solve(1, 1, n, 0, 0);
int mn = 0x7fffffff;
rep(i, 0, n)mn = min(mn, f[1][i]);
printf("%d\n",mn);return 0;
}