P4297 [NOI2006]网络收费 [树形dp]

网 络 收 费 网络收费

点击标题链接查看题目.


最 初 想 法 \color{blue}{最初想法}

类似线段树建出一颗树, 暴力枚举每个节点改不改 .


正 解 部 分 \color{red}{正解部分}

观察题目的表格, 可以发现:

  • 满足 N a < N b Na < Nb Na<Nb 的子树上, 只有 A A A 收费方式需要付费 .
  • 满足 N a ≥ N b Na \geq Nb NaNb 的子树上, 只有 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 sizeiij<i+j
  • N a ≥ N b Na \geq Nb NaNb 需要满足条件 s i z e i − i − j ≥ i + j size_i-i-j \geq i+j sizeiiji+j

这样往下递归, 每次 假定 当前节点 N a < N b Na<Nb Na<Nb 还是 N a ≥ N b Na \geq Nb NaNb.

到了叶子节点就可以直接计算这个叶子节点产生的贡献了, 具体地说:

设当前递归到了 叶子节点 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=0max_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=0max_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 NaNb, 只有 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_depi1 .


#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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值