hdu4897(树链剖分)

启迪:果然,离成功就差一步,如果刚刚我放弃了,停下debug的步伐,那我终不会知道成功原来并不遥远,就在霎那间,在我执着的信念前它终会出现


题目:树链剖分,细节真多,思路有一些乱,不过只要把所有情况考虑上了就ok了

注意事项在代码中


#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
inline int read()
{
	int ans,f=1;char ch;
	while ((ch=getchar())<'0'||ch>'9') if (ch=='-') f=-1;ans=ch-'0';
	while ((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-'0';
	return ans*f;
}
const int N=300090;
int to[N*2],pre[N*2],head[N*2],btot;//注意双向边数组开两倍
void addedge(int x,int y) {to[++btot]=y;pre[btot]=head[x];head[x]=btot;}// add edge
int dep[N],fa[N],top[N],size[N],son[N],id[N],tot;//tree devide
struct aa
{
	int l,r,sum,rev;
	int kk;//判断这个区间有没有被翻转
}a[N*4];//线段树开4倍
void init()//清空
{
	memset(head,0,sizeof(head));btot=0;
	memset(fa,0,sizeof(fa));
	memset(top,0,sizeof(top));
	memset(size,0,sizeof(size));
	memset(son,0,sizeof(son));
	memset(id,0,sizeof(id));tot=0;
	memset(dep,0,sizeof(dep));
	memset(a,0,sizeof(a));
}

int n,m;
void dfs1(int u,int depth,int f)
{
	fa[u]=f;dep[u]=depth;size[u]=1;
	int max_size=0;son[u]=0;
	for (int i=head[u];i;i=pre[i])
	if (to[i]!=f)
	{
		dfs1(to[i],depth+1,u);
		size[u]+=size[to[i]];
		if (size[to[i]]>max_size) max_size=size[to[i]],son[u]=to[i];
	}
}
void dfs2(int u,int anc)
{
	top[u]=anc;id[u]=++tot;
	if (son[u]) dfs2(son[u],anc);
	for (int i=head[u];i;i=pre[i])
	if (to[i]!=fa[u]&&to[i]!=son[u]) dfs2(to[i],to[i]);
}//树链剖分
void build(int i,int l,int r)
{
	a[i].l=l;a[i].r=r;
	if (l==r) return;
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
}//建立线段树,把所有节点建立
void up(int i) 
{
	a[i].sum=0;
	if (a[i<<1].l) a[i].sum+=a[i<<1].sum;
	if (a[i<<1|1].l) a[i].sum+=a[i<<1|1].sum;//注意要保证有这个孩子再加,启下
}
void down(int i)
{
	if (a[i].rev)
	{
		a[i].rev=0;a[i<<1].rev^=1;a[i<<1|1].rev^=1;
		a[i<<1].sum=(a[i<<1].r-a[i<<1].l+1)-a[i<<1].sum;//因为,当i为子节点时,这个地方可能导致空节点num不为0,承上
		a[i<<1|1].sum=(a[i<<1|1].r-a[i<<1|1].l+1)-a[i<<1|1].sum;
	}
}
void rever(int i,int l,int r)//线段树中旋转区间
{
	down(i);
	if (a[i].l==l&&a[i].r==r) 
	{
		a[i].rev^=1;
		a[i].sum=(a[i].r-a[i].l+1)-a[i].sum;
		return;
	}
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) rever(i<<1,l,r);
	else if (mid<l) rever(i<<1|1,l,r);
	else rever(i<<1,l,mid),rever(i<<1|1,mid+1,r);
	up(i);
}
void rever(int x,int y)//树上旋转x~y的路径,——询问1
{
	while (top[x]!=top[y])
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		rever(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	if (x!=y) rever(1,id[x]+1,id[y]);//因为一个点代表它到father的这条边
}
void bj(int i,int l,int r)
{
	if (a[i].l==l&&a[i].r==r) 
	{
		a[i].kk^=1;
		return;
	}
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) bj(i<<1,l,r);
	else if (mid<l) bj(i<<1|1,l,r);
	else bj(i<<1,l,mid),bj(i<<1|1,mid+1,r);
}
int pan(int i,int x)
{
	int ans=a[i].kk;
	if (a[i].l==a[i].r) return ans;
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=x)  return ans^pan(i<<1,x);else return ans^pan(i<<1|1,x);//一个点它是否被反转,就是从线段树根节点到这个子节点有多少包括他的区间被整体反转这样统计是否反转时,只需用从根查到子节点就好
}
int find(int i,int l,int r)
{
	down(i);
	if (a[i].l==l&&a[i].r==r) return a[i].sum;
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) return find(i<<1,l,r);
	if (mid<l) return find(i<<1|1,l,r);
	return find(i<<1,l,mid)+find(i<<1|1,mid+1,r);
}
void query(int x,int y)//x~y的黑边数量——询问3
{
	int ans=0;
	while (top[x]!=top[y])
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		if (x!=top[x]) ans+=find(1,id[top[x]]+1,id[x]);//处理重边的黑边number
		ans+=(pan(1,id[fa[top[x]]])^find(1,id[top[x]],id[top[x]]));//pan判断父亲的标记,find边本身的标记,取异或,是否被变为黑色
		x=fa[top[x]];//上面一定要是id【fa。。】我们把树信息移到线段树中了,操作都应该在线段树中修改
	}
	if (dep[x]>dep[y]) swap(x,y);
	if (x!=y) ans+=find(1,id[x]+1,id[y]);
	printf("%d\n",ans);
}
void near_rever(int x,int y)//路径邻边更改——询问2。。。。。实际上就是把一些边直接反转,一些边标记反转
{
	while (top[x]!=top[y])
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		bj(1,id[top[x]],id[x]);//标记这些节点的所有轻儿子的边都被反转
		if (son[x]) rever(1,id[son[x]],id[son[x]]);//如果有重儿子直接反转
		rever(1,id[top[x]],id[top[x]]);//把最上面的轻边反转,和下一次循环时标记反转最底下的边抵消
		x=fa[top[x]];
	}
	if (dep[x]>dep[y])swap(x,y);
	bj(1,id[x],id[y]);
	rever(1,id[x],id[x]);//把最上面的边反转
	if (son[y]) rever(1,id[son[y]],id[son[y]]);
}
int main()
{
	int T=read();
	while (T--)
	{
		init();
		n=read();
		int x,y;
		for (int i=1;i<n;i++) 
		{
			x=read();y=read();
			addedge(x,y);addedge(y,x);
		}//add edge
		dfs1(1,1,0);
		dfs2(1,1);
		build(1,1,tot);
		int q=read(),t;
		while (q--)
		{
			t=read(),x=read(),y=read();
			switch(t)
			{
				case 1:rever(x,y);break;
				case 2:near_rever(x,y);break;
				case 3:query(x,y);break;//after switch,we must break!!! 
			}
		}
	}
	return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值