BZOJ4538:[Hnoi2016]网络 (整体二分+Lca+树状数组/线段树+路径交/树链剖分+Heap)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4538


题目分析:这题网上好多人写树剖啊,都是把一条路径的区间搞出来之后取反更新,删除的话就套个Heap或者写线段树CDQ分治blablabla,时间复杂度 O ( n l o g 3 ( n ) ) O(nlog^3(n)) O(nlog3(n)),好像因为树剖和Heap的常数特别小所以根本不虚。网上某大神用这种方法8200ms就过了,而我写了个 O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))的线段树+路径交稳稳地T掉了,我写的另一个 O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))的整体二分+树状数组用了11000msQAQ。可能是出题人为了卡暴力,特地将树构造成接近链的形状,结果正中树剖下怀之类的……
其实我一开始就想的是树剖,这应该是一个比较暴力的想法:用树剖将某条路径变成DFS序上不超过log(n)段区间,然后将这log(n)段区间取反,即取补集,可以证明取反后也只有log(n)段区间。将当前路径的重要程度值加进线段树的这些区间,线段树的每个节点再维护一棵Treap,以支持删除操作(当然,Treap换成Heap常数更小)。还可以写线段树CDQ分治。对于出现时间跨越了整个当前时间段[L,R]的边,直接加进去维护最大值,用完之后打一个懒惰标记清空线段树即可。


当然,也并不是没有时间复杂度更小的方法,我们可以用整体二分。假设二分答案的区间为[L,R],其中间值为mid,那就将所有重要程度值>mid的路径上面的点权值全部+1,并记录当前存在几条路径,假设查询的时候,某个点的权值=当前存在路径数,就说明所有重要程度值>mid的路径都包含该点,就要向左边递归,否则递归右边。路径加法单点查询可以转化为单点加法子树查询,用树状数组即可做到 O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))

CODE(整体二分+Lca+树状数组):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<stdio.h>
using namespace std;

const int maxn=100100;
const int maxl=20;

struct edge
{
	int obj;
	edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

struct data
{
	int Type,u,v,w,val;
} work[maxn<<2];

int bit[maxn];
int num=0;

int fa[maxn][maxl];
int dep[maxn];

int st[maxn];
int ed[maxn];
int Time=0;

int a[maxn<<2];
bool vis[maxn<<2];
bool Left[maxn<<2];
int b[maxn<<2];

int n,m;

void Add(int x,int y)
{
	cur++;
	e[cur].obj=y;
	e[cur].Next=head[x];
	head[x]=e+cur;
}

void Dfs(int node)
{
	st[node]=++Time;
	for (edge *p=head[node]; p; p=p->Next)
	{
		int son=p->obj;
		if (son!=fa[node][0])
		{
			fa[son][0]=node;
			dep[son]=dep[node]+1;
			Dfs(son);
		}
	}
	ed[node]=Time;
}

int Lca(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	for (int j=maxl-1; j>=0; j--)
		if (dep[ fa[x][j] ]>=dep[y]) x=fa[x][j];
	if (x==y) return x;
	for (int j=maxl-1; j>=0; j--)
		if (fa[x][j]!=fa[y][j])
			x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}

void Update(int x,int v)
{
	while (x<=n+1)
	{
		bit[x]+=v;
		x+=(x&(-x));
	}
}

int Sum(int x)
{
	int sum=0;
	while (x)
	{
		sum+=bit[x];
		x-=(x&(-x));
	}
	return sum;
}

void Binary(int L,int R,int x,int y)
{
	if (L==R)
	{
		for (int i=x; i<=y; i++)
		{
			int z=a[i],k=work[z].Type;
			if (k==0)
			{
				num++;
				Update(st[ work[z].u ]+1,1);
				Update(st[ work[z].v ]+1,1);
				Update(st[ work[z].w ]+1,-1);
				Update(st[ fa[ work[z].w ][0] ]+1,-1);
			}
			if (k==1)
			{
				num--;
				z=work[z].u;
				Update(st[ work[z].u ]+1,-1);
				Update(st[ work[z].v ]+1,-1);
				Update(st[ work[z].w ]+1,1);
				Update(st[ fa[ work[z].w ][0] ]+1,1);
			}
			if (k==2)
			{
				int temp=Sum(ed[ work[z].u ]+1)-Sum(st[ work[z].u ]);
				if (temp<num) work[z].val=L;
				else work[z].val=-1;
			}
		}
		return;
	}
	
	int mid=(L+R)>>1;
	for (int i=x; i<=y; i++)
	{
		Left[i]=true;
		int z=a[i],k=work[z].Type;
		if ( k==0 && work[z].val>mid )
		{
			Left[i]=false;
			num++;
			Update(st[ work[z].u ]+1,1);
			Update(st[ work[z].v ]+1,1);
			Update(st[ work[z].w ]+1,-1);
			Update(st[ fa[ work[z].w ][0] ]+1,-1);
		}
		if ( k==1 && work[ work[z].u ].val>mid )
		{
			Left[i]=false;
			num--;
			z=work[z].u;
			Update(st[ work[z].u ]+1,-1);
			Update(st[ work[z].v ]+1,-1);
			Update(st[ work[z].w ]+1,1);
			Update(st[ fa[ work[z].w ][0] ]+1,1);
		}
		if (k==2)
		{
			int temp=Sum(ed[ work[z].u ]+1)-Sum(st[ work[z].u ]);
			if (temp<num) Left[i]=false;
		}
	}
	
	int cnt1=0;
	for (int i=x; i<=y; i++) if (Left[i]) b[++cnt1]=a[i];
	int cnt2=cnt1;
	for (int i=x; i<=y; i++) if (!Left[i]) b[++cnt2]=a[i];
	for (int i=x; i<=y; i++) Left[i]=false;
	for (int i=1; i<=cnt2; i++) a[x+i-1]=b[i];
	
	if (cnt1) Binary(L,mid,x,x+cnt1-1);
	if (cnt1<cnt2) Binary(mid+1,R,x+cnt1,y);
}

int main()
{
	freopen("4538.in","r",stdin);
	freopen("4538.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	for (int i=1; i<=n; i++) head[i]=NULL;
	for (int i=1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Add(x,y);
		Add(y,x);
	}
	
	Dfs(1);
	
	for (int j=1; j<maxl; j++)
		for (int i=1; i<=n; i++)
			fa[i][j]=fa[ fa[i][j-1] ][j-1];
	
	for (int i=1; i<=m; i++)
	{
		scanf("%d",&work[i].Type);
		if (work[i].Type==0)
		{
			scanf("%d%d%d",&work[i].u,&work[i].v,&work[i].val);
			work[i].w=Lca(work[i].u,work[i].v);
			vis[i]=true;
		}
		else
		{
			scanf("%d",&work[i].u);
			if (work[i].Type==1) vis[ work[i].u ]=false;
		}
	}
	
	for (int i=1; i<=m; i++) if (vis[i])
		work[++m].Type=1,work[m].u=i;
	for (int i=1; i<=m; i++) a[i]=i;
	Binary(1,1e9,1,m);
	
	for (int i=1; i<=m; i++)
		if (work[i].Type==2) printf("%d\n",work[i].val);
	return 0;
}

还有一种做法,和整体二分的思想差不多,时间复杂度也一样(但常数根本不是一个级别,我估计下面的代码常数是2000左右)。我们将所有路径按重要程度值插入Treap里面,并且在Treap的每一个节点里维护它子树中所有路径的交集。查询一个点的时候就二叉查找,优先考虑右子树,但如果该点在右子树的所有路径的交集上,就要往左走。由于路径交是log(n)的,所以总时间是 O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))的。用Treap的话就可以处理强制在线的情况,但由于平衡树是动态的,每一次左旋右旋都要重做一次路径交,代价很高,所以写离线加线段树会更好,然而还是各种TTTTT……
(为了安(qi)慰(pian)自己码code的时间没有白费,在此将TLE的代码也贴一贴,已通过对拍OwO)

CODE(线段树+路径交):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxl=20;

struct Tnode
{
	int U,V,cnt;
} tree[maxn<<3];
int bot[maxn<<1];

struct edge
{
	int obj;
	edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

struct data
{
	int Type,u,v,val,id,Time;
} work[maxn<<1];

struct kscla
{
	int num;
	kscla *Q_Next;
} Q[maxn];
int P[maxn];

int fa[maxn][maxl];
int dep[maxn];

int tp[6];
int sum;
int n,m;

int Get(int x)
{
	int y=0;
	while ( (1<<y)!=x ) y++;
	return y;
}

void Add(int x,int y)
{
	cur++;
	e[cur].obj=y;
	e[cur].Next=head[x];
	head[x]=e+cur;
}

void Dfs(int node)
{
	for (edge *p=head[node]; p; p=p->Next)
	{
		int son=p->obj;
		if (son!=fa[node][0])
		{
			fa[son][0]=node;
			dep[son]=dep[node]+1;
			Dfs(son);
		}
	}
}

bool Comp1(data x,data y)
{
	if ( (!x.Type) && y.Type ) return true;
	if ( x.Type && (!y.Type) ) return false;
	return (x.val<y.val);
}

bool Comp2(data x,data y)
{
	return (x.Time<y.Time);
}

void Swap(int &x,int &y)
{
	int z=x;
	x=y;
	y=z;
}

int Lca(int x,int y)
{
	if (dep[x]<dep[y]) Swap(x,y);
	for (kscla *p=Q+(dep[x]-dep[y]); p!=Q; p=p->Q_Next)
		x=fa[x][p->num];
	if (x==y) return x;
	for (int j=P[ dep[x] ]; j>=0; j--)
		if (fa[x][j]!=fa[y][j])
			x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}

bool Check(int a,int x,int y,int z)
{
	int b=Lca(a,x);
	int c=Lca(a,y);
	if (b!=z) Swap(b,c);
	return ( (b==z) && (c==a) );
}

bool Judge(int x,int y,int a,int b,int lca)
{
	if ( !Check(x,a,b,lca) ) return false;
	return Check(y,a,b,lca);
}

void Work(int x,int y,int z,int w,int &a,int &b)
{
	if ( (!x) || (!z) )
	{
		a=b=0;
		return;
	}
	
	tp[0]=Lca(x,y);
	tp[1]=Lca(x,z);
	tp[2]=Lca(x,w);
	tp[3]=Lca(y,z);
	tp[4]=Lca(y,w);
	tp[5]=Lca(z,w);
	
	a=b=0;
	int len=-1;
	for (int i=0; i<5; i++)
		for (int j=i+1; j<6; j++)
		{
			int k=Lca(tp[i],tp[j]);
			int l=dep[ tp[i] ]+dep[ tp[j] ]-(dep[k]<<1);
			if (l<=len) continue;
			if ( !Judge(tp[i],tp[j],x,y,tp[0]) ) continue;
			if ( !Judge(tp[i],tp[j],z,w,tp[5]) ) continue;
			a=tp[i],b=tp[j],len=l;
		}
}

void Update(int root,int L,int R,int x,int nu,int nv)
{
	if ( x<L || R<x ) return;
	if ( x==L && R==x )
	{
		tree[root].U=nu;
		tree[root].V=nv;
		if (nu) tree[root].cnt++;
		else tree[root].cnt--;
		return;
	}
	
	int mid=(L+R)>>1;
	int Left=root<<1;
	int Right=Left|1;
	
	Update(Left,L,mid,x,nu,nv);
	Update(Right,mid+1,R,x,nu,nv);
	
	if (!tree[Left].cnt)
	{
		tree[root]=tree[Right];
		return;
	}
	if (!tree[Right].cnt)
	{
		tree[root]=tree[Left];
		return;
	}
	tree[root].cnt=tree[Left].cnt+tree[Right].cnt;
	Work(tree[Left].U,tree[Left].V,tree[Right].U,tree[Right].V,tree[root].U,tree[root].V);
}

int Query(int root,int L,int R,int x)
{
	if (L==R)
	{
		if (L!=1) return work[ bot[L] ].val;
		if (!tree[root].cnt) return -1;
		int W=Lca(tree[root].U,tree[root].V);
		if ( Check(x,tree[root].U,tree[root].V,W) ) return -1;
		return work[ bot[1] ].val;
	}
	
	int mid=(L+R)>>1;
	int Left=root<<1;
	int Right=Left|1;
	
	if (!tree[Right].cnt) return Query(Left,L,mid,x);
	if (!tree[Right].U) return Query(Right,mid+1,R,x);
	int W=Lca(tree[Right].U,tree[Right].V);
	if ( Check(x,tree[Right].U,tree[Right].V,W) ) return Query(Left,L,mid,x);
	return Query(Right,mid+1,R,x);
}

int main()
{
	freopen("4538.in","r",stdin);
	freopen("4538.out","w",stdout);
	
	P[0]=-1;
	for (int i=1; i<maxn; i++)
	{
		P[i]=P[i>>1]+1;
		int x=i&(-i);
		Q[i].num=Get(x);
		Q[i].Q_Next=Q+(i-x);
	}
	
	scanf("%d%d",&n,&m);
	for (int i=1; i<=n; i++) head[i]=NULL;
	for (int i=1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Add(x,y);
		Add(y,x);
	}
	
	Dfs(1);
	
	for (int j=1; j<maxl; j++)
		for (int i=1; i<=n; i++)
			fa[i][j]=fa[ fa[i][j-1] ][j-1];
	
	for (int i=1; i<=m; i++)
	{
		scanf("%d",&work[i].Type);
		if (!work[i].Type) scanf("%d%d%d",&work[i].u,&work[i].v,&work[i].val);
		else scanf("%d",&work[i].u);
		work[i].Time=i;
	}
	sort(work+1,work+m+1,Comp1);
	for (sum=1; sum<=m; sum++)
		if (!work[sum].Type) work[sum].id=sum,bot[sum]=work[sum].Time;
		else break;
	sum--;
	sort(work+1,work+m+1,Comp2);
	
	for (int i=1; i<=m; i++)
	{
		if (!work[i].Type) Update(1,1,sum,work[i].id,work[i].u,work[i].v);
		if (work[i].Type==1) Update(1,1,sum,work[ work[i].u ].id,0,0);
		if (work[i].Type==2) printf("%d\n", Query(1,1,sum,work[i].u) );
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值