2016多校联合第二场 HDU5735 B. Born Slippy 【树状dp剪枝优化】

博客解析了HDU5735 B. Born Slippy题目,介绍了如何利用树状动态规划(DP)和剪枝优化来解决权值序列子集的最大累加和问题。通过从根节点向下更新,维护每个节点到根节点路径上的最大操作和,并在过程中判断是否需要跳过某些节点以减少无效计算,从而避免超时。算法复杂度优化到了线性级别。

题目:

Born Slippy

Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 223    Accepted Submission(s): 65


Problem Description
Professor Zhang has a rooted tree, whose vertices are conveniently labeled by  1,2,...,n . And the  i -th vertex is assigned with weight  wi .

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  vi1   (1<im) .
2. the value  f(s)=wv1+i=2mwvi opt wvi1  is maximum. Operation  x opt y  denotes bitwise AND, OR or XOR operation of two numbers.
 

Input
There are multiple test cases. The first line of input contains an integer  T , indicating the number of test cases. For each test case:

The first line contains an integer  n  and a string  opt   (2n216,opt{AND,OR,XOR} ) -- the number of vertices and the operation. The second line contains  n  integers  w1,w2,...,wn   (0wi<216) . The thrid line contain  n1  integers  f2,f3,...,fn   (1fi<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 .
 

Output
For each test case, output an integer  S=(i=1nif(i)) mod (109+7) .
 

Sample Input
  
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
 

Sample Output
  
91 139 195
 

Author
zimpha
 

Source

比完多校之后去看了一下题解,发现这道题的官方题解比较复杂,所以在博客上发一下自己的思路。


首先明确一下题意,给出一颗树,每个节点有一个权值。

对于每个节点 i ,从它开始一直到根节点所有元素存在一个序列(即一路找爸爸找到根节点,途径的所有点),求一个该序列的子集,使得子集中每两个相邻点权值的某种操作(按位或,按位与,按位异或)的累加和最大,记为 F(i)

最后求  S=(i=1nif(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";
	}
}




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值