网 络 收 费 网络收费 网络收费
点击标题链接查看题目.
最 初 想 法 \color{blue}{最初想法} 最初想法
类似线段树建出一颗树, 暴力枚举每个节点改不改 .
正 解 部 分 \color{red}{正解部分} 正解部分
观察题目的表格, 可以发现:
- 满足 N a < N b Na < Nb Na<Nb 的子树上, 只有 A A A 收费方式需要付费 .
- 满足 N a ≥ N b Na \geq Nb Na≥Nb 的子树上, 只有 B B B 收费方式需要付费 .
但是此题为两两配对产生贡献,
直接计算 时间复杂度为
O
(
N
2
)
O(N^2)
O(N2), 考虑将两个点拆开一个一个去计算 .
每个 非叶节点 都表示了若干点对的
L
C
A
LCA
LCA, 结合上方观察得出的结论, 每个节点对答案造成的贡献都可看作在 非叶节点 造成的 .
则设
c
o
s
t
[
i
,
j
]
cost[i, j]
cost[i,j] 表示
i
i
i 这个 叶节点, 在深度为
j
j
j 的 祖先(非叶节点) 中与若干点配对产生的 总流量(费用), 可以
O
(
N
2
)
O(N^2)
O(N2) 预处理.
设
F
[
i
,
j
]
F[i, j]
F[i,j] 表示 以
i
i
i 号节点为根的子树, 管辖的叶子节点中含有
j
j
j 个
B
B
B 收费方式叶子节点 的 最小花费,
则
F
[
k
,
i
+
j
]
=
min
(
F
[
l
e
f
t
_
s
o
n
i
,
i
]
+
F
[
r
i
g
h
t
_
s
o
n
i
,
j
]
)
c
n
t
b
∈
[
0
,
s
i
z
e
i
]
F[k, i+j]=\min(F[left\_son_i, i]+F[right\_son_i,j])\ \ \ \ \ \ \ cnt_b∈[0,size_i]
F[k,i+j]=min(F[left_soni,i]+F[right_soni,j]) cntb∈[0,sizei]
- 若 N a < N b Na < Nb Na<Nb 需要满足条件 s i z e i − i − j < i + j size_i-i-j<i+j sizei−i−j<i+j
- 若 N a ≥ N b Na \geq Nb Na≥Nb 需要满足条件 s i z e i − i − j ≥ i + j size_i-i-j \geq i+j sizei−i−j≥i+j
这样往下递归, 每次 假定 当前节点 N a < N b Na<Nb Na<Nb 还是 N a ≥ N b Na \geq Nb Na≥Nb.
到了叶子节点就可以直接计算这个叶子节点产生的贡献了, 具体地说:
设当前递归到了 叶子节点 a a a,
- 若当前为 A A A 付费方式, 对答案的贡献为 F [ a , 0 ] = ∑ i = 0 m a x _ d e p ! t m p [ i ] ∗ c o s t [ a , i ] F[a,0]=\sum\limits_{i=0}^{max\_dep} !tmp[i]*cost[a, i] F[a,0]=i=0∑max_dep!tmp[i]∗cost[a,i] .
- 若当前为 B B B 付费方式, 对答案的贡献为 F [ a , 1 ] = ∑ i = 0 m a x _ d e p t m p [ i ] ∗ c o s t [ a , i ] F[a,1]=\sum\limits_{i=0}^{max\_dep}tmp[i]*cost[a, i] F[a,1]=i=0∑max_deptmp[i]∗cost[a,i] .
在上方的 答案统计中, 当且仅当 目前收费方式与 初始收费方式 不同时, 需要加上修改产生的花费 .
实 现 部 分 \color{red}{实现部分} 实现部分
对收费方式的处理: 使用 C [ 0 / 1 ] [ i ] C[0/1][i] C[0/1][i] 表示第 i i i 个用户使用 A / B A/B A/B 收费方式时的 修改 费用, 递归到叶子节点时赋初值 可以方便地处理 修改产生的费用 .
- t m p [ d e p ] = 0 tmp[dep] = 0 tmp[dep]=0 表示当前 D F S 链 DFS链 DFS链 中深度为 d e p dep dep 的 当前节点 以下 N a < N b Na<Nb Na<Nb, 只有 A A A 付费.
- t m p [ d e p ] = 1 tmp[dep] = 1 tmp[dep]=1 则表示 N a ≥ N b Na \geq Nb Na≥Nb, 只有 B B B 付费 .
求解
L
C
A
LCA
LCA 深度时, 可以不断 “试” 两个待测节点
x
,
y
x, y
x,y, 具体地说:
不断的对
x
,
y
x, y
x,y 执行
>
>
i
>>i
>>i 操作, 相当于向上走
i
i
i 步, 当它们走到的节点不同时, 那两个不同的节点的 父亲 即为
L
c
a
Lca
Lca, 深度为
m
a
x
_
d
e
p
−
i
−
1
max\_dep-i-1
max_dep−i−1 .
#include<bits/stdc++.h>
#define reg register
typedef long long ll;
const int maxn = 3500;
int n;
int N;
int tmp[13];
int Fs[maxn];
ll C[2][maxn];
ll F[maxn][maxn];
ll cost[maxn][13];
int Lca(int x, int y){
for(reg int i = n; i >= 0; i --)
if((x>>i) != (y>>i)) return n-i-1;
}
void DFS(int k, int dep, int l, int r){
if(dep == n){
F[k][0] = C[0][l], F[k][1] = C[1][l];
for(reg int i = 0; i < dep; i ++) F[k][tmp[i]] += cost[l][i];
return ;
}
int mid = l+r >> 1;
memset(F[k], 0x3f, sizeof F[k]);
tmp[dep] = 0;
DFS(k<<1, dep+1, l, mid), DFS(k<<1|1, dep+1, mid+1, r);
for(reg int i = 0; i <= mid-l+1; i ++)
for(reg int j = 0; j <= mid-l+1; j ++){
if(r-l+1-i-j >= i+j) continue ;
F[k][i+j] = std::min(F[k][i+j], F[k<<1][i] + F[k<<1|1][j]);
}
tmp[dep] = 1;
DFS(k<<1, dep+1, l, mid), DFS(k<<1|1, dep+1, mid+1, r);
for(reg int i = 0; i <= mid-l+1; i ++)
for(reg int j = 0; j <= mid-l+1; j ++){
if(r-l+1-i-j < i+j) continue ;
F[k][i+j] = std::min(F[k][i+j], F[k<<1][i] + F[k<<1|1][j]);
}
}
int main(){
scanf("%d", &n); N = 1 << n;
for(reg int i = 1; i <= N; i ++) scanf("%d", &Fs[i]);
for(reg int i = 1; i <= N; i ++) scanf("%lld", &C[!Fs[i]][i]), C[Fs[i]][i] = 0;
for(reg int i = 1; i <= N; i ++)
for(reg int j = i+1; j <= N; j ++){
int x; scanf("%d", &x);
int lca_dep = Lca(i-1, j-1);
cost[i][lca_dep] += x, cost[j][lca_dep] += x;
}
DFS(1, 0, 1, N);
ll Ans = 1e18;
for(reg int i = 0; i <= N; i ++) Ans = std::min(Ans, F[1][i]);
printf("%lld\n", Ans);
return 0;
}