1. 程式人生 > >【bzoj1495】[NOI2006]網絡收費 暴力+樹形背包dp

【bzoj1495】[NOI2006]網絡收費 暴力+樹形背包dp

遞歸 兩種 highlight fin esp 統計 付費 main div

題目描述

給出一個有 $2^n$ 個葉子節點的完全二叉樹。每個葉子節點可以選擇黑白兩種顏色。

對於每個非葉子節點左子樹中的葉子節點 $i$ 和右子樹中的葉子節點 $j$ :
如果 $i$ 和 $j$ 的顏色都為當前節點子樹中顏色較多(相等視為白色)的那個,則不需要付出代價;
都為較小的那個則需要付 $2f[i][j]$ 的代價;
否則需要付 $f[i][j]$ 。

求最小代價。

輸入

輸入文件中第一行有一個正整數N。 第二行有2N個整數,依次表示1號,2號,…,2N號用戶註冊時的付費方式,每一個數字若為0,則表示對應用戶的初始付費方式為A,否則該數字為1,表示付費方式為B。 第三行有2N個整數,表示每一個用戶修改付費方式需要支付的費用,依次為C1, C2, …,CM 。( M=2N ) 以下2N-1行描述給定的兩兩用戶之間的流量表F,總第(i + 3)行第j列的整數為Fi, j+i 。(1≤i<2N,1≤j≤2N ? i) 所有變量的含義可以參見題目描述。N≤10,0≤Fi, j≤500,0≤Ci≤500 000

輸出

你的程序只需要向輸出文件輸出一個整數,表示NS中學支付給網絡公司的最小總費用。(單位:元)

樣例輸入

2
1 0 1 0
2 2 10 9
10 1 2
2 1
3

樣例輸出

8


題解

暴力+樹形背包dp

先Orz一發CQzhangyu

首先觀察付出代價的方式,可以改看作為:對於 $i$ ,選了顏色較少的那個則需要付出 $f[i][j]$ 的代價。這樣我們就把兩個點之間的代價轉化為了單個點的代價。

然後由於這麽多狀態難以統計,因此需要提前計算代價。

設 $f[i][j]$ 表示點 $i$ 為根的子樹內有 $j$ 個葉子節點選了黑色的最小總代價。那麽這是一個樹形背包問題,遞歸左右子樹後跑背包合並即可。

然而這裏有一個非常大的問題:代價的類型是與顏色較少還是較多有關的。

CQzhangyu給出的解法是:暴力枚舉這兩種情況,分別留下有用部分。由於是完全二叉樹,因此有遞歸式:$T(1)=\log n,T(n)=4T(\frac n2)+O(n^2)$ ,不考慮 $T(1)$ 時根據主定理解得 $T(n)=O(n^2\log n)$ ,單獨考慮 $T(1)$ ,1被考慮了 $n^2$ 次,因此也是 $O(n^2\log n)$ 。

因此直接暴力的復雜度是對的。

時間復雜度 $O(2^{2n}·n)$ 。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1030
#define ls x << 1
#define rs x << 1 | 1
using namespace std;
typedef long long ll;
int n , m , a[N] , t[N];
ll c[N] , w[N][N] , v[N][N] , f[N << 1][N];
void init(int x , int d)
{
	if(!d) return;
	int i , j;
	init(ls , d - 1) , init(rs , d - 1);
	for(i = x << d ; i < ((x << 1) + 1) << (d - 1) ; i ++ )
		for(j = ((x << 1) + 1) << (d - 1) ; j < (x + 1) << d ; j ++ )
			v[i - m][x] += w[i - m][j - m] , v[j - m][x] += w[i - m][j - m];
}
void dfs(int x , int d)
{
	int i , j;
	if(!d)
	{
		f[x][a[x - m]] = 0;
		f[x][a[x - m] ^ 1] = c[x - m];
		for(i = x >> 1 ; i ; i >>= 1) f[x][t[i]] += v[x - m][i];
		return;
	}
	memset(f[x] , 0x3f , sizeof(ll) * ((1 << d) + 1));
	t[x] = 1 , dfs(ls , d - 1) , dfs(rs , d - 1);
	for(i = 0 ; i <= 1 << (d - 1) ; i ++ )
		for(j = 0 ; j <= (1 << (d - 1)) - i ; j ++ )
			f[x][i + j] = min(f[x][i + j] , f[ls][i] + f[rs][j]);
	t[x] = 0 , dfs(ls , d - 1) , dfs(rs , d - 1);
	for(i = 1 ; i <= 1 << (d - 1) ; i ++ )
		for(j = (1 << (d - 1)) - i + 1 ; j <= 1 << (d - 1) ; j ++ )
			f[x][i + j] = min(f[x][i + j] , f[ls][i] + f[rs][j]);
}
int main()
{
	int i , j;
	ll ans = 1ll << 62;
	scanf("%d" , &n) , m = 1 << n;
	for(i = 0 ; i < m ; i ++ ) scanf("%d" , &a[i]);
	for(i = 0 ; i < m ; i ++ ) scanf("%lld" , &c[i]);
	for(i = 0 ; i < m ; i ++ )
		for(j = i + 1 ; j < m ; j ++ )
			scanf("%lld" , &w[i][j]);
	init(1 , n);
	dfs(1 , n);
	for(i = 0 ; i <= m ; i ++ ) ans = min(ans , f[1][i]);
	printf("%lld\n" , ans);
	return 0;
}

【bzoj1495】[NOI2006]網絡收費 暴力+樹形背包dp