【HDU】5735 Born Slippy DP+折半枚举优化
题意
给出一个具有 n 个节点的树,每个节点都有一个权值 w,现在对于每个节点 s 要求出一个\(f(s)\)
- 对于节点 s,找到一个节点序列,\(v_1,v_2,v_3...v_m\),\(v_1 = s\),\(v_{i+1}\)是\(v_i\)的祖先节点
- \(f(s)=w_s+\sum_{i=2}^{m}{w_{v_i}\ opt\ w_{v_{i-1}}} f(s) 要尽可能的大\)
输出\(\sum_{i=1}^n i*f(i)\)
思路
我们用 \(dp[i]\) 表示\(f(i)-w_i\)
先不考虑树上,把题目转移到一个整数数组上来。
那么 \(dp[i]=max(dp[i],dp[j]+w_j\ opt \ \ w_i)(j<i)\)
树上同理,但是这个转移方程的复杂度太高了。
愣是想不到如何把 \(O(n^2)\) 变成 \(O(logn)\)。
看题解发现是真滴骚。
在我理解来,就是把遍历 \(j\) 的过程变成了遍历 \(w_j\) 的可能取值,但是 \(w_j\) 有 \(2^{16}\)种情况,也不可能遍历,我们通过一个数组 \(f[a][b]\) 变得只需枚举 \(2^8\) 来降低复杂度 。
总体复杂度为 \(N*2^8\)即\(2^{24}\)
代码
/*
* @Autor: valk
* @Date: 2020-08-11 12:38:37
* @LastEditTime: 2020-09-28 17:19:07
* @Description: 如果邪恶 是华丽残酷的乐章 它的终场 我会亲手写上 晨曦的光 风干最后一行忧伤 黑色的墨 染上安详
*/
#include <algorithm>
#include <iostream>
#include <map>
#include <math.h>
#include <queue>
#include <set>
#include <stack>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int mod = 1e9 + 7;
const int seed = 12289;
const double eps = 1e-6;
const ll INF=0x8f8f8f8f8f8f8f8f;//-1886417009
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10;
vector<int>vec[N];
int w[N];
ll rel[N][260],f[260][260],ans;
char op[5];
int opt(int a,int b)
{
if(op[0]=='A') return a&b;
if(op[0]=='X') return a^b;
return a|b;
}
void dfs(int pos)
{
int a=w[pos]>>8,b=w[pos]&255;
ll maxn=0;
for(int i=0;i<256;i++){
if(f[i][b]!=INF)
maxn=max(maxn,f[i][b]+(opt(i,a)<<8));
}
ans=(ans+1LL*(maxn+w[pos])%mod*pos%mod)%mod;
for(int i=0;i<256;i++){
rel[pos][i]=f[a][i];
f[a][i]=max(f[a][i],maxn+opt(b,i));
}
for(int i=0;i<vec[pos].size();i++){
dfs(vec[pos][i]);
}
for(int i=0;i<256;i++){
f[a][i]=rel[pos][i];
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d%s",&n,op);
ans=0;
memset(f,INF,sizeof(f));
for(int i=1;i<=n;i++){
vec[i].clear();
}
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
}
for(int i=2;i<=n;i++){
int fa;
scanf("%d",&fa);
vec[fa].pb(i);
}
dfs(1);
printf("%lld\n",ans);
}
return 0;
}