P4297 [NOI2006] 树形 DP

题意

传送门 P4297 [NOI2006] 网络收费

题解

对于非叶子节点,若 n A < n B nA < nB nA<nB,令其状态为 A A A,反之为 B B B。那么两个用户对应流量的费用可以拆分为单个用户的贡献,即当用户 v v v 与其祖先节点 u u u 状态相同时, v v v 贡献为 v → u v\rightarrow u vu 的流量。

祖先节点的状态依赖于叶子节点的状态,而叶子节点的贡献依赖于祖先节点的状态。那么先自顶而下地枚举祖先节点的状态,在自底向上地通过树形 DP 进行合并。

根据主定理,总时间复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MAXV = 1 << 10, SZ = MAXV << 1;
constexpr ll INF = 0x3f3f3f3f3f3f3f3f;
int N, A[MAXV], C[MAXV];
int F[MAXV][MAXV];
int sum[MAXV][MAXV + 1];
ll rec[SZ][MAXV];

void _min(ll &x, ll y) { x = min(x, y); }

void solve(int k, int l, int r, int d, int s)
{
    if (r - l == 1)
    {
        rec[k][0] = A[l] ? C[l] : 0;
        rec[k][1] = A[l] ? 0 : C[l];
        int _l = 0, _r = 1 << N;
        for (int i = 0; i < d; ++i)
        {
            int _m = (_l + _r) / 2;
            int d;
            if (l < _m)
            {
                d = sum[l][_r] - sum[l][_m];
                _r = _m;
            }
            else
            {
                d = sum[l][_m] - sum[l][_l];
                _l = _m;
            }
            rec[k][s >> i & 1] += d;
        }
        return;
    }
    for (int i = 0; i <= r - l; ++i)
        rec[k][i] = INF;
    int m = (l + r) / 2, chl = k * 2 + 1, chr = k * 2 + 2;
    solve(chl, l, m, d + 1, s | 1 << d);
    solve(chr, m, r, d + 1, s | 1 << d);
    int len = (r - l) / 2;
    for (int na = 0; na < r - l - na; ++na)
        for (int nl = max(0, na - len); nl <= min(len, na); ++nl)
            _min(rec[k][na], rec[chl][nl] + rec[chr][na - nl]);
    solve(chl, l, m, d + 1, s);
    solve(chr, m, r, d + 1, s);
    for (int na = len; na <= r - l; ++na)
        for (int nl = max(0, na - len); nl <= min(len, na); ++nl)
            _min(rec[k][na], rec[chl][nl] + rec[chr][na - nl]);
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> N;
    int n = 1 << N;
    for (int i = 0; i < n; ++i)
        cin >> A[i], A[i] ^= 1;
    for (int i = 0; i < n; ++i)
        cin >> C[i];
    for (int i = 0; i < n; ++i)
        for (int j = 1; i + j < n; ++j)
            cin >> F[i][i + j], F[i + j][i] = F[i][i + j];
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            sum[i][j + 1] = sum[i][j] + F[i][j];
    solve(0, 0, n, 0, 0);
    ll res = INF;
    for (int i = 0; i <= n; ++i)
        _min(res, rec[0][i]);
    cout << res << '\n';
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值