bzoj3252攻略 贪心+dfs序+线段树

题目链接:戳这里

3252: 攻略

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 605   Solved: 255
[ Submit][ Status][ Discuss]

Description

题目简述:树版[k取方格数]
 
众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏。
今天他得到了一款新游戏《XX半岛》,这款游戏有n个场景(scene),某些场景可以通过不同的选择支到达其他场景。所有场景和选择支构成树状结构:开始游戏时在根节点(共通线),叶子节点为结局。每个场景有一个价值,现在桂马开启攻略之神模式,同时攻略k次该游戏,问他观赏到的场景的价值和最大是多少(同一场景观看多次是不能重复得到价值的)
“为什么你还没玩就知道每个场景的价值呢?”
“我已经看到结局了。”

Input

第一行两个正整数n,k
第二行n个正整数,表示每个场景的价值
以下n-1行,每行2个整数a,b,表示a场景有个选择支通向b场景(即a是b的父亲)
保证场景1为根节点

Output

 
输出一个整数表示答案

Sample Input

5 2
4 3 2 1 1
1 2
1 5
2 3
2 4

Sample Output

10

HINT

对于100%的数据,n<=200000,1<=场景价值<=2^31-1

emmm...这题一开始不太会做,后来想想发现挺简单的。

一开始的想法,这题可以转换为两种操作:一种是求点权和最大的树链,一种是把一条树链全部置为0。

然后就发现好麻烦,普通的树链剖分求不了点权和最大的树链啊,陷入江局。

如果把问题这么想就变简单了,如果每个节点存储从根到该节点的价值和,那么每次找到最大的叶节点,然后更改取走该叶节点所在链后受到影响的节点的值。

至此,此题大体转换为以下过程:

1、每次贪心的取走值最大的树链,即叶节点,能证明这是正确的。

2、修改受到影响的节点的值。

那么哪些节点受到影响了呢?

只考虑深度大于它的节点,将树上的节点分为两类:在它的子树内和不在它的子树内。

显然如果一个节点不在该节点的子树内,那么该节点到根的路径上也不会经过该节点,故不会受到影响。

如果一个节点在它的子树内,因为不能重复累加价值,所以该节点到根的路径便会少val[i]的价值,减去即可。

所以问题最终转化为:

1、每次贪心的取走值最大的树链,即叶节点。

2,从叶节点遍历到根节点,对于遍历到的每个点,把它子树内的所有点全部减去这个点的价值。

维护子树内的最大值和加减显然是dfs序+线段树的经典问题。完美解决。

注意ans,sum要开long long!

代码:

#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
typedef long long LL;
int read()
{
	char c;int sum=0,f=1;c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
	return sum*f;
}
int n,k;
int a[maxn],fa[maxn];
struct node{
	int l,r;
	int pos;
	LL maxa,lazy;
}tree[maxn<<2];
int in[maxn],out[maxn],dfn[maxn],cur;
LL sum[maxn],ans;
bool vis[maxn];
vector<int> edge[maxn];
void dfs(int x)
{
	in[x]=++cur;dfn[cur]=x;
	int lens=edge[x].size();
	for(int i=0;i<lens;i++)
	{
		int nex=edge[x][i];
		sum[nex]=sum[x]+a[nex];
		dfs(nex);
	}
	out[x]=cur;
}
void pushup(int id)
{
	tree[id].maxa=0;
	int ls=id<<1,rs=id<<1|1;
	if(tree[id].maxa<tree[ls].maxa){tree[id].maxa=tree[ls].maxa;tree[id].pos=tree[ls].pos;}
	if(tree[id].maxa<tree[rs].maxa){tree[id].maxa=tree[rs].maxa;tree[id].pos=tree[rs].pos;}
}
void build(int id,int l,int r)
{
	tree[id].l=l,tree[id].r=r;tree[id].lazy=0;
	if(l==r)
	{
		tree[id].maxa=sum[dfn[l]];
		tree[id].pos=l;
		return;
	}
	int mid=l+r>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	pushup(id);
}
void pushdown(int id)
{
	int ls=id<<1,rs=id<<1|1;
	tree[ls].lazy+=tree[id].lazy;
	tree[rs].lazy+=tree[id].lazy;
	tree[ls].maxa-=tree[id].lazy;
	tree[rs].maxa-=tree[id].lazy;
	tree[id].lazy=0;
}
void add(int id,int ql,int qr,LL x)
{
	int l=tree[id].l,r=tree[id].r;
	if(l==ql && r==qr)
	{
		tree[id].lazy+=x;
		tree[id].maxa-=x;
		return;
	}
	pushdown(id);
	int mid=l+r>>1;
	if(qr<=mid) add(id<<1,ql,qr,x);
	else if(ql>mid) add(id<<1|1,ql,qr,x);
	else add(id<<1,ql,mid,x),add(id<<1|1,mid+1,qr,x);
	pushup(id);
}
int main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
	a[i]=read();
	sum[1]=a[1];
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		edge[u].push_back(v);
		fa[v]=u;
	}
	dfs(1);
	build(1,1,n);
	while(k--)
	{
		ans+=tree[1].maxa;
		for(int i=dfn[tree[1].pos];!vis[i] && i;i=fa[i])
		{
			vis[i]=true;
			add(1,in[i],out[i],a[i]);
		}
	}
	printf("%lld\n",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值