题目:
Born Slippy
For each s∈{1,2,...,n} , Professor Zhang wants find a sequence of vertices v1,v2,...,vm such that:
1. v1=s and vi is the ancestor of vi−1 (1<i≤m) .
2. the value f(s)=wv1+∑i=2mwvi opt wvi−1 is maximum. Operation x opt y denotes bitwise AND, OR or XOR operation of two numbers.
The first line contains an integer n and a string opt (2≤n≤216,opt∈{AND,OR,XOR} ) -- the number of vertices and the operation. The second line contains n integers w1,w2,...,wn (0≤wi<216) . The thrid line contain n−1 integers f2,f3,...,fn (1≤fi<i) , where fi is the father of vertex i .
There are about 300 test cases and the sum of n in all the test cases is no more than 106 .
3 5 AND 5 4 3 2 1 1 2 2 4 5 XOR 5 4 3 2 1 1 2 2 4 5 OR 5 4 3 2 1 1 2 2 4
91 139 195
比完多校之后去看了一下题解,发现这道题的官方题解比较复杂,所以在博客上发一下自己的思路。
首先明确一下题意,给出一颗树,每个节点有一个权值。
对于每个节点 i ,从它开始一直到根节点所有元素存在一个序列(即一路找爸爸找到根节点,途径的所有点),求一个该序列的子集,使得子集中每两个相邻点权值的某种操作(按位或,按位与,按位异或)的累加和最大,记为 F(i)
最后求 S=(∑i=1ni⋅f(i)) mod (109+7)
首先想到的是树状dp,即从根向下更新,dp[i]记录从该点向上一直到根节点的路径上能得到最大的权值计算和。由于从上向下更新,计算dp[i]时,所有下标小于 i 节点的 dp 值
已经全部被计算出了。
那么dp[i]就等于 max(dp[i的某个祖先]+operate(该祖先的权值,i的权值)) 其中 operate可以是 按位或,按位与,按位异或 由题目决定。算法复杂度为O(n^2)
但由于题目中点数有 1e6 个,只要数据中有一条长边超过1e4的长度就会超时了。
所以我们继续研究发现,从某节点开始到根节点的路径长度是一定的,假设这条路径上有N个点,其累加和最多包含 N-1 个数字。如果我们取的 dp 转移位置是第2个祖先而不是第
一个的话,累加和将会减少一个数字,即operate(祖先2,祖先1)
也就是,当且仅当我们发现,operate(权值i,k代祖先) 的值很大,大到超过跳过的所有 operate(i代祖先,i-1代祖先) 时才会使用该点更新 dp 值。
而由于每个点的值不超过 2^16 ,而两个 不超过 2^16 的数字之间按位与、或、异或的值都是不会超过 2^16 的,故我们可以再更新某个点dp值时维护一个sum变量,计算
它跳过的所有相邻祖先之间的operate值之和,当sum超过2^16时,无论怎样都不可能更新到更优解了,此时直接剪枝。
#define _CRT_SECURE_NO_WARNINGS
#define author ReskiP
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
#include <iostream>
using namespace std;
#define maxn 100000
#define mod 1000000007;
#define maxxs 1<<16
int wei[maxn];//权值
int fat[maxn];//存父亲
long long temans[maxn];//每个点的最大路径和
int ops;
int op(int a, int b)
{
switch (ops)
{
case 0:
return a & b;
case 1:
return a | b;
case 2:
return a ^ b;
}
}
void findp(int x)
{
int now = x;
long long sum = 0;
while (1)
{
sum += op(wei[now], wei[fat[now]]);//维护sum值
now = fat[now];
temans[x] = max(temans[x], temans[now] + op(wei[now], wei[x]));
if (now == 1||sum>(maxxs))//遍历到根节点或sum超限时剪枝
{
return;
}
}
}
int main()
{
long long ans;
int t;
int n;
char opr[10];
cin >> t;
while (t--)
{
memset(temans, 0, sizeof(temans));
ans = 0;
cin >> n;
cin >> opr;
switch (opr[0])
{
case 'A':
ops = 0;
break;
case 'O':
ops = 1;
break;
case 'X':
ops = 2;
break;
}
for (int i = 1; i <= n; i++)
{
scanf("%d", &wei[i]);
}
for (int i = 2; i <= n; i++)
{
scanf("%d", &fat[i]);
}
for (int i = 2; i <= n; i++)
{
findp(i);
}
for (int i = 1; i <= n; i++)
{
ans += ((temans[i] + wei[i])*i) % mod;
ans %= mod;
}
cout << ans << "\n";
}
}
博客解析了HDU5735 B. Born Slippy题目,介绍了如何利用树状动态规划(DP)和剪枝优化来解决权值序列子集的最大累加和问题。通过从根节点向下更新,维护每个节点到根节点路径上的最大操作和,并在过程中判断是否需要跳过某些节点以减少无效计算,从而避免超时。算法复杂度优化到了线性级别。
822

被折叠的 条评论
为什么被折叠?



