【BZOJ4538】网络(HNOI2016)-整体二分+树上差分+树状数组

测试地址:网络
做法: 本题需要用到整体二分+树上差分+树状数组。
各位大佬想的都是用一些树链剖分+线段树套堆这种诡异操作,O(mlog3n)O(m\log^3n)卡进去的,然而像我这种常数爆大选手根本不敢写…于是发现还有22log\log的方法,还是挺神的。
考虑只有一个询问的时候,二分答案,每次二分一个midmid,要知道这个询问的答案是不是大于midmid,只要把前面那些大于midmid的修改计算一下,看到这个询问的时刻时,是不是所有存在的路径都过这个点,如果不是就说明这个询问答案大于midmid,否则相反。
我们发现涉及到的操作和询问都随着二分的答案区间变化,于是很自然地想到整体二分。于是在判定的时候,用树上差分转化成单点修改子树询问,拍到DFS序上用树状数组维护即可,时间复杂度为O(mlognlogMAX)O(m\log n\log MAX)MAXMAX为最大的答案),可以通过此题。
…然而现实非常骨感,如果每次用到LCA都倍增算一次的话,就会被卡爆,然而Tarjan又难写,为原已近200行的代码又贡献几十行,但我们发现要求的都是询问中路径的LCA,所以一开始处理好就可以卡进去了。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,first[100010]={0},tot=0;
int dep[100010]={0},fa[100010][20]={0},in[100010],out[100010],tim=0;
int LCA[200010],id[200010],tmpl[200010],tmpr[200010];
int ans[200010]={0},sum[100010]={0},cnt;
struct edge
{
	int v,next;
}e[200010];
struct oper
{
	int op,a,b,v;
}q[200010];

int read()
{
	char c;
	int s=0;
	c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();
	return s;
}

void insert(int a,int b)
{
	e[++tot].v=b;
	e[tot].next=first[a];
	first[a]=tot;
}

void dfs(int v)
{
	in[v]=++tim;
	for(int i=first[v];i;i=e[i].next)
		if (e[i].v!=fa[v][0])
		{
			dep[e[i].v]=dep[v]+1;
			fa[e[i].v][0]=v;
			dfs(e[i].v);
		}
	out[v]=tim;
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int x,int d)
{
	for(int i=x;i<=n;i+=lowbit(i))
		sum[i]+=d;
}

int calc_sum(int x)
{
	int ans=0;
	for(int i=x;i;i-=lowbit(i))
		ans+=sum[i];
	return ans;
}

int lca(int a,int b)
{
	if (dep[a]<dep[b]) swap(a,b);
	for(int i=17;i>=0;i--)
		if (dep[fa[a][i]]>=dep[b])
			a=fa[a][i];
	if (a==b) return a;
	for(int i=17;i>=0;i--)
		if (fa[a][i]!=fa[b][i])
			a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}

void modify(int x,int d)
{
	int g=LCA[x],a=q[x].a,b=q[x].b;
	add(in[a],d),add(in[b],d),add(in[g],-d);
	if (fa[g][0]) add(in[fa[g][0]],-d);
}

void solve(int l,int r,int ql,int qr)
{
	if (ql>qr) return;
	if (l==r)
	{
		if (l==0)
		{
			cnt=0;
			for(int i=ql;i<=qr;i++)
			{
				if (q[id[i]].op==0) cnt++,modify(id[i],1);
				if (q[id[i]].op==1) cnt--,modify(id[i],-1);
				if (q[id[i]].op==2)
				{
					int now=calc_sum(out[q[id[i]].a])-calc_sum(in[q[id[i]].a]-1);
					if (now<cnt) ans[id[i]]=0;
					else ans[id[i]]=-1;
				}
			}
			for(int i=ql;i<=qr;i++)
			{
				if (q[id[i]].op==0) modify(id[i],-1);
				if (q[id[i]].op==1) modify(id[i],1);
			}
		}
		else
		{
			for(int i=ql;i<=qr;i++)
				if (q[id[i]].op==2) ans[id[i]]=l;
		}
		return;
	}
	
	int mid=(l+r)>>1,tl=0,tr=0;
	cnt=0;
	for(int i=ql;i<=qr;i++)
	{
		if (q[id[i]].op==0)
		{
			if (q[id[i]].v>mid)
				cnt++,modify(id[i],1),tmpr[++tr]=id[i];
			else tmpl[++tl]=id[i];
		}
		if (q[id[i]].op==1)
		{
			if (q[id[i]].v>mid)
				cnt--,modify(id[i],-1),tmpr[++tr]=id[i];
			else tmpl[++tl]=id[i];
		}
		if (q[id[i]].op==2)
		{
			int now=calc_sum(out[q[id[i]].a])-calc_sum(in[q[id[i]].a]-1);
			if (now<cnt) tmpr[++tr]=id[i];
			else tmpl[++tl]=id[i];
		}
	}
	for(int i=ql;i<=qr;i++)
	{
		if (q[id[i]].op==0&&q[id[i]].v>mid) modify(id[i],-1);
		if (q[id[i]].op==1&&q[id[i]].v>mid) modify(id[i],1);
	}
	
	for(int i=1;i<=tl;i++) id[ql+i-1]=tmpl[i];
	for(int i=1;i<=tr;i++) id[ql+tl+i-1]=tmpr[i];
	solve(l,mid,ql,ql+tl-1);
	solve(mid+1,r,ql+tl,qr);
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<n;i++)
	{
		int a,b;
		a=read(),b=read();
		insert(a,b),insert(b,a);
	}
	dep[0]=-1;
	dfs(1);
	for(int i=1;i<=17;i++)
		for(int j=1;j<=n;j++)
			fa[j][i]=fa[fa[j][i-1]][i-1];
	int mxv=0;
	for(int i=1;i<=m;i++)
	{
		q[i].op=read();
		id[i]=i;
		if (q[i].op==0)
		{
			q[i].a=read(),q[i].b=read(),q[i].v=read();
			mxv=max(mxv,q[i].v);
			LCA[i]=lca(q[i].a,q[i].b);
		}
		if (q[i].op==1)
		{
			int t;
			t=read();
			q[i].a=q[t].a,q[i].b=q[t].b,q[i].v=q[t].v;
			LCA[i]=LCA[t];
		}
		if (q[i].op==2)
			q[i].a=read();
	}
	
	solve(0,mxv,1,m);
	for(int i=1;i<=m;i++)
		if (q[i].op==2) printf("%d\n",ans[i]);
	
	return 0;
}
posted @ 2018-09-19 22:24  Maxwei_wzj  阅读(187)  评论(0编辑  收藏  举报