bzoj-3307 雨天的尾巴

141 篇文章 0 订阅
88 篇文章 0 订阅

题意:

给出一个n个点的树,有m个事件;

每次是将x到y的路径上所有点都投放一个z的物品;

求最后每个点上那个物品最多;

n,m<=100000;


题解:

一道数据结构好题;

首先这道题问的是最终答案,那么把所有事件离线下来处理;

考虑如果树是一条链,那么对于这样一个序列问题怎么做?

将左端点x设置z数量加一,右端点y设为z数量-1;

然后用一个权值线段树去扫整个序列,查询最大值即为答案;

时间复杂度O((n+m)logm),空间复杂度O(mlogm);

十分优雅的做法,我们把它转移到树上;

树上的标记与序列大致相同,我们从叶节点向上扫整棵树;

那么在x,y结点标z数量加一,LCA(x,y),fa(LCA(x,y))设置z数量-1;

这似乎是树链问题转化很常用的手段之一?

然后我们一样的向上扫。。。两个点扫到了一个根!

现在要把这两个线段树合并起来;

如果直接启发式合并复杂度有点奇怪吧,不过权值线段树有更加优越的方法;

直接递归合并!

如果两个树有一个没结点,直接返回有结点的那个;

如果都有结点就递归计算,然后将计算出来的新结点返回,两个旧结点回收;

这个的复杂度是总体是O(nlogn)的(单次可能O(n)但是总体卡不住);

以下是PoPoQQQ大爷的精彩证明[鼓掌熊]:

我定义一棵线段树的势为节点个数

那么合并两棵线段树的代价为 两棵线段树的势和-新线段树的势

然后一开始所有线段树的势之和为O(nlogn)

一个线段树上有logn个节点

合并到最后有n个节点

所以最后的势能差是O(nlogn)

完事了

然后这题就能时间空间都是nlogn了

证明了复杂度可靠,那这题就置剩下实现的细节问题了;

别的倒是没什么,但是这题爆栈!爆栈!!!

然后改个宽搜就过了= =,真是够了;


代码:


#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110000
#define MEM 2000000
#define M 1000000000
#define lson l,mid,ls[no]
#define rson mid+1,r,rs[no]
using namespace std;
int fa[N][30],deep[N],size[N],ts[N],ans[N];
int next[N<<2],kind[N<<2],head[N],ce;
bool val[N<<2];
int ma[MEM],kma[MEM],ls[MEM],rs[MEM],root[N],tot;
int mp[MEM],top;
queue<int>q;
int newseg()
{
	if(top)	return mp[top--];
	return ++tot;
}
void delseg(int no)
{
	mp[++top]=no;
	ls[no]=0;
	rs[no]=0;
	ma[no]=0;
}
void Pushup(int no)
{
	if(ma[ls[no]]>=ma[rs[no]])
		ma[no]=ma[ls[no]],kma[no]=kma[ls[no]];
	else
		ma[no]=ma[rs[no]],kma[no]=kma[rs[no]];
}
void update(int l,int r,int &no,int k,int val)
{
	if(!no)	no=newseg();
	if(l==r)
		ma[no]+=val,kma[no]=l;
	else
	{
		int mid=l+r>>1;
		if(k<=mid)	update(lson,k,val);
		else		update(rson,k,val);
		Pushup(no);
	}
	if(!ma[no])
		kma[no]=0;
}
int merge(int l,int r,int nol,int nor)
{
	if(!nol||!nor)	return nol+nor;
	int no=newseg();
	if(l==r)
	{
		ma[no]=ma[nol]+ma[nor];
		kma[no]=l;
	}
	else
	{
		int mid=l+r>>1;
		ls[no]=merge(l,mid,ls[nol],ls[nor]);
		rs[no]=merge(mid+1,r,rs[nol],rs[nor]);
		Pushup(no);
	}
	delseg(nol),delseg(nor);
	return no;
}
namespace Graph
{
	int to[N<<1],next[N<<1],head[N],ce;
	int tq[N],st,en;
	void add(int x,int y)
	{
		to[++ce]=y;
		next[ce]=head[x];
		head[x]=ce;
	}
	void bfs()
	{
		tq[st=en=1]=1;
		int i,x;
		while(st<=en)
		{
			x=tq[st++];
			size[x]=1,deep[x]=deep[fa[x][0]]+1;
			for(i=1;fa[x][i-1];i++)
				fa[x][i]=fa[fa[x][i-1]][i-1];
			for(i=head[x];i;i=next[i])
			{
				if(to[i]!=fa[x][0])
				{
					fa[to[i]][0]=x;
					tq[++en]=to[i];
				}
			}
		}
		while(en)
		{
			size[fa[tq[en]][0]]+=size[tq[en]];
			if(size[tq[en]]==1)
				q.push(tq[en]);
			en--;
		}
	}
};
int LCA(int x,int y)
{
	if(deep[x]<deep[y])
		swap(x,y);
	int k=20;
	while(deep[x]!=deep[y])
	{
		if(deep[fa[x][k]]>=deep[y])
			x=fa[x][k];
		k--;
	}
	if(x==y)	return x;
	k=20;
	while(k>=0)
	{
		if(fa[x][k]!=fa[y][k])
			x=fa[x][k],y=fa[y][k];
		k--;
	}
	return fa[x][0];
}
void Insert(int x,int k,bool v)
{
	val[++ce]=v;
	kind[ce]=k;
	next[ce]=head[x];
	head[x]=ce;
}
int main()
{
	int n,m,i,j,k,l,x,y,z;
	scanf("%d%d",&n,&m);
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		Graph::add(x,y);
		Graph::add(y,x);
	}
	Graph::bfs();
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		l=LCA(x,y);
		Insert(x,z,1),Insert(y,z,1);
		Insert(l,z,0),Insert(fa[l][0],z,0);
	}
	while(!q.empty())
	{
		x=q.front(),q.pop();
		for(i=head[x];i;i=next[i])
			update(1,M,root[x],kind[i],val[i]?1:-1);
		ans[x]=kma[root[x]];
		root[fa[x][0]]=merge(1,M,root[x],root[fa[x][0]]);
		ts[fa[x][0]]+=size[x];
		if(ts[fa[x][0]]+1==size[fa[x][0]])
			q.push(fa[x][0]);
	}
	for(i=1;i<=n;i++)
		printf("%d\n",ans[i]);
	return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值