【树形DP && O(n^2) 到 O(n^(3/2))巧妙优化】HDU - 5735 Born Slippy

Step1 Problem:

给你一棵 n 个节点的树以及点权 w[i], 给你 opt, 定义 f(s) 为 s 的祖先序列(包括 s) opt 和最大值 + w[s].
输出 S = sum(s * f(s)), s = 1, 2, 3, …, n.
数据范围:
2 <= n <= 2^16, 0 <= w[i] < 2^16, opt = 与 或 异或 其中之一.

Step2 Ideas:

学习博客
正常思路:dp[i] = max(dp[i], dp[j] + opt(w[i], w[j])); O(n^2) 显然超时
dp[j] + opt(w[i], w[j]) = dp[j] + (opt(w[i]前八位, w[j]前八位)<<8) + opt(w[i]后八位, w[j]后八位);
我们可以先求出 dp[j] + opt(w[i]后八位, w[j]后八位) 或者 dp[j] + (opt(w[i]前八位, w[j]前八位)<<8) 由于学习博客是 dp[j] + opt(w[i]后八位, w[j]后八位) 所以我们将这个,另外一个也是可行的。
令:ds[x][y] = max(dp[x][y], dp[j] + opt(w[i]后八位, w[j]后八位));
第一步:求 ds[x][y]; 已知 dp[j], w[j]后八位; 枚举 w[i] 后八位 O(256)的复杂度求出 ds[x][y];
第二步:求 dp[i]; 已知 ds[x][y], w[i]前八位; 枚举 w[j] 前八位 O(256)的复杂度求出 dp[i];
时间优化成了 O(n^(3/2))
讨论 ds[x][y]:x 和 y 分别代表什么
我们需要 第一步和第二步有联系:
所以 x 和 y 得是枚举的对象:x 代表 w[j] 前八位, y 代表 w[i] 后八位. 反过来也行.

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = (1<<16)+5;
const int M = 1<<8;
const int MOD = 1e9+7;
int w[N], fq[N], n;
ll ds[M][M], now[N][M];//ds[x][y] x w[j]前八位, y w[i]后八位, dp[j] + opt(w[j]&255, w[i]&255)
int vis[M];
ll ans;
vector<int> Map[N];
char op[50];
int opt(int x, int y)
{
    if(op[0] == 'A') return x&y;
    else if(op[0] == 'X') return x^y;
    else return x|y;
}
void dfs(int u)
{
    int A = w[u]>>8, B = w[u]&255;
    ll dp = 0;
    for(int i = 0; i < 256; i++)//当前是 i, 枚举 w[j] 前八位
        if(vis[i]) dp = max(dp, ds[i][B]+(opt(i, A)<<8));
    ans += (1LL*(dp+w[u])%MOD*u)%MOD, ans %= MOD;
    for(int i = 0; i < 256; i++)//当前是 j, 枚举 w[i] 的后八位
    {
        now[u][i] = ds[A][i];
        ds[A][i] = max(ds[A][i], dp+opt(B, i));
    }
    vis[A]++;
    for(int i = 0; i < Map[u].size(); i++)
    {
        int to = Map[u][i];
        dfs(to);
    }
    vis[A]--;
    for(int i = 0; i < 256; i++)
        ds[A][i] = now[u][i];
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        ans = 0;
        scanf("%d %s", &n, op);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", w+i);
            Map[i].clear();
        }
        for(int i = 2; i <= n; i++)
        {
            scanf("%d", &fq[i]);
            Map[fq[i]].push_back(i);
        }
        memset(vis, 0, sizeof(vis));
        memset(ds, 0, sizeof(ds));
        dfs(1);
        printf("%lld\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值