P5327 [ZJOI2019]语言 解题报告

oj:https://gxyzoj.com/d/gxyznoi/p/P35

树上差分+线段树合并+树链剖分

1. 暴力

从最基础的方法开始,暴力统计s-t上的点,然后用map直接统计,显然会T,20pts

2. 特殊性质

测试点5,6保证了数据是一条链,将链上每一个节点编号为1-n

对于每一次操作,可以记录其覆盖情况,设共有x个节点,有y个节点已被覆盖,则答案需增加\((x-1)\times(x-y)\)

如上操作,显然可以用线段树维护覆盖情况

3. 正解

利用虚树思想,若一个节点v可以与u进行贸易,则从u向v连一条边,则这棵生成树的大小-1就是可以与u进行贸易的节点个数

可以将链的方法迁移到树上

首先,对原树求dfs序,然后对于每次操作进行树剖,将每一次操作分成一些连续的区间

再考虑如何用线段树维护,转变一下思维,可以将记录是否被覆盖的数组改为记录覆盖次数

所以可以利用树上差分,在s,t处对所有区间分别加一,再在\(fa_{lca}\)处减去即可

然后考虑如何转移,对于每个节点,连接状态显然分为两部分

一部分是该点本身的权值,直接将预处理好的区间相加即可

另一部分是由儿子转移而来,这部分可以使用线段树合并解决

最后统计答案,每次转移后的的个数显然就是覆盖次数不为0的点数,可以另开一个数组val记录

注意,要用动态开点线段树,数组尽量开到n的128倍以上,不要写一些奇怪的语句

4. 代码

#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,head[100005],edgenum;
struct edge{
	int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int dep[100005],son[100005],size[100005],top[100005],fa[100005];
struct node{
	int l,r,val;
};
struct node1{
	int l,r;
};
vector<node> a[100005];
vector<node1> tmp;
void dfs(int u,int f)
{
	size[u]=1;
	fa[u]=f;
	dep[u]=dep[f]+1;
	int maxn=-1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa[u]) continue;
		dfs(v,u);
		size[u]+=size[v];
		if(maxn<size[v])
		{
			maxn=size[v];
			son[u]=v;
		}
	}
}
int dfn[100005],idx,pre[100005];
void dfs2(int u,int t)
{
//	printf("%d %d\n",u,t);
	dfn[u]=++idx;
	top[u]=t;
	pre[idx]=u;
	a[u].push_back((node){dfn[u],dfn[u],1});
	if(fa[u])
	{
		a[fa[u]].push_back((node){dfn[u],dfn[u],-1});
	}
	if(son[u])
	{
		dfs2(son[u],t);
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int lca(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tmp.push_back((node1){dfn[top[u]],dfn[u]});
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	tmp.push_back((node1){dfn[u],dfn[v]});
	return u;
}
ll lazy[12800005];
int val[12800005],ls[12800005],rs[12800005];
void pushup(int id,int l,int r)
{
	if(lazy[id]>0) val[id]=r-l+1;
	else val[id]=val[ls[id]]+val[rs[id]];
}
int cnt;
int update(int id,int l,int r,int ql,int qr,int v)
{
	if(!id) id=++cnt;
	if(l>=ql&&r<=qr)
	{
		lazy[id]+=v;
		pushup(id,l,r);
		return id;
	}
	int mid=(l+r)>>1;
	if(ql<=mid)
	ls[id]=update(ls[id],l,mid,ql,qr,v);
	if(qr>mid)
	rs[id]=update(rs[id],mid+1,r,ql,qr,v);
	pushup(id,l,r);
	return id;
}
int merge(int p,int q,int l,int r)
{
	if(p==0||q==0) return p+q;
	lazy[p]+=lazy[q];
	if(l==r)
	{
		pushup(p,l,r);
		return p;
	}
	int mid=(l+r)>>1;
	ls[p]=merge(ls[p],ls[q],l,mid);
	rs[p]=merge(rs[p],rs[q],mid+1,r);
	pushup(p,l,r);
	return p;
}
ll ans;
int rt[100005];
void solve(int u)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa[u]) continue;
		solve(v);
		rt[u]=merge(rt[u],rt[v],1,n);
	}
	for(int i=0;i<a[u].size();i++)
	{
		rt[u]=update(rt[u],1,n,a[u][i].l,a[u][i].r,a[u][i].val);
	}
	ans=ans+val[rt[u]];
}
int main()
{
//	freopen("1.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
		rt[i]=i;
	}
	rt[n]=n;
	cnt=n;
	dfs(1,0);
//	printf("1");
	dfs2(1,1);
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		int lc=lca(u,v);
		for(int j=0;j<tmp.size();j++)
		{
			int l1=tmp[j].l,r1=tmp[j].r;
			if(l1>r1) swap(l1,r1);
			a[u].push_back((node){l1,r1,1});
			a[v].push_back((node){l1,r1,1});
			if(fa[lc]) a[fa[lc]].push_back((node){l1,r1,-2});
		}
		tmp.clear();
	}
	solve(1);
	printf("%lld",(ans-n)/2);
	return 0;
}
posted @ 2024-04-10 10:47  wangsiqi2010916  阅读(33)  评论(1编辑  收藏  举报