1. 程式人生 > >[bzoj3252]攻略_dfs序_線段樹_貪心

[bzoj3252]攻略_dfs序_線段樹_貪心

攻略 bzoj-3252

題目大意:給定一棵n個節點的有根樹,點有點權。讓你選出至多k個節點,使得他們到根的鏈的並最大。

註釋:$1\le n\le 2\cdot 10^5$,$1\le val_i\le 2^{31}-1$。


想法:這題模擬賽T2,正解可並堆,我用的$dfs$序加線段樹。

考慮暴力:顯然每次選取當前貢獻最大的一定是最優的,一個非常顯然的貪心。

我們對每個節點維護一個$dis$表示當前節點選取的貢獻。如果我們選取了一個節點$x$那麼以$x$為根的子樹都會減去$val_x$。

顯然一個節點只會被選取一次,所以如果我們每次可以快速找到最大值的位置,我們就可以對以$x$為根的子樹進行子樹減。

故此我們線上段樹上的每個節點都維護一個$son$資訊,表示線段樹上的最大值是左兒子貢獻的還是右兒子貢獻的。

對於線段樹上的葉子節點再維護一下這個葉子對應原樹的哪個節點。

這樣的話每次我們從根開始通過那個$son$資訊找到最大的貢獻節點,然後從當前節點開始向根遍歷知道遇到被選取過的節點。每遍歷到一個節點都進行一遍子樹減即可。

總時間複雜度$O(nlogn)$。

Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010 
#define ls p<<1
#define rs p<<1|1
using namespace std; typedef long long ll;
struct Node
{
	ll mx,del; int id,son;
}a[N<<2];
ll dis[N]; int dic[N],cnt,val[N],re[N],f[N],size[N];
int to[N<<1],nxt[N<<1],head[N],tot;
bool vis[N];
inline char nc() {static char *p1,*p2,buf[100000]; return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;}
ll rd() {ll x=0; char c=nc(); while(!isdigit(c)) c=nc(); while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=nc(); return x;}
inline void add(int x,int y) {to[++tot]=y; nxt[tot]=head[x]; head[x]=tot;}
inline void pushup(int p)
{
	a[p].mx=max(a[ls].mx,a[rs].mx);
	a[p].son=(a[ls].mx>=a[rs].mx?1:2);
}
inline void pushdown(int p)
{
	if(!a[p].del) return;
	a[ls].mx+=a[p].del; a[ls].del+=a[p].del;
	a[rs].mx+=a[p].del; a[rs].del+=a[p].del;
	a[p].del=0;
}
void build(int l,int r,int p)
{
	if(l==r) {a[p].mx=dis[re[l]],a[p].id=re[l]; return;}
	int mid=(l+r)>>1;
	build(l,mid,ls); build(mid+1,r,rs);
	pushup(p);
}
void update(int x,int y,ll val,int l,int r,int p)
{
	if(x<=l&&r<=y)
	{
		a[p].mx+=val; a[p].del+=val;
		return;
	}
	int mid=(l+r)>>1; pushdown(p);
	if(x<=mid) update(x,y,val,l,mid,ls);
	if(mid<y) update(x,y,val,mid+1,r,rs);
	pushup(p);
}
int query_son(int l,int r,int p)
{
	if(l==r) return p;
	int mid=(l+r)>>1; pushdown(p);
	if(a[p].son==1) return query_son(l,mid,ls);
	else return query_son(mid+1,r,rs);
}
void dfs(int pos,int fa)
{
	dis[pos]=dis[fa]+val[pos];
	f[pos]=fa;
	dic[pos]=++cnt; re[cnt]=pos;
	size[pos]=1;
	for(int i=head[pos];i;i=nxt[i]) if(to[i]!=fa)
	{
		dfs(to[i],pos);
		size[pos]+=size[to[i]];
	}
}
int main()
{
	ll ans=0;
	int n=rd(),k=rd();
	for(int i=1;i<=n;i++) val[i]=rd();
	for(int i=1;i<n;i++)
	{
		int x=rd(),y=rd(); add(x,y); add(y,x);
	}
	dfs(1,0);
	build(1,n,1);
	while(k--)
	{
		int p=query_son(1,n,1);
		ans+=a[p].mx;
		int x=a[p].id;
		while(x&&!vis[x])
		{
			update(dic[x],dic[x]+size[x]-1,-val[x],1,n,1); val[x]=0;
			vis[x]=true;
			x=f[x];
		}
	}
	cout << ans << endl ;
	// fclose(stdin); fclose(stdout);
	return 0;
}

小結:從暴力入手是一個非常好的選擇。