Loading

【题解】[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;
}
posted @ 2021-10-04 16:56  7KByte  阅读(84)  评论(0编辑  收藏  举报