[bzoj2589]Count on a tree II

23 篇文章 0 订阅
1 篇文章 0 订阅

Count on a tree II

题解

看到这道题,如果不加强制在线什么的,应该是很容易想到树上莫队的,根据欧拉序很容易解决。

可是由于要强制在线,就不能离线下来做了,于是我们就想到了用树上分块来进行处理。

容易证明,如果我们在一棵节点数为n的树上选择一些点使得树上任意一点到被选择点的距离不会超过S,那么一定存在一种方法使得选择的点数不会超过\left \lfloor \frac{n}{S} \right \rfloor个。

如果我们从深度最大且未被覆盖的点开始,往上找最多S个点,将最远处即与那个点距离达到S的祖先或是根节点的点选择,则一定可以使当前的选择成为这个点子树上的最优解,因为其子树上不会存在点的贡献比它大。

于是,我们就可以先这样选出\left \lfloor \frac{n}{S} \right \rfloor个点,将被选择的点两点中间的用bitset维护颜色。这样的话预处理出每个被选择的点到其祖先中的被选择的点的时间复杂度是O\left(\frac{n^2}{S}+\frac{n^3}{wS^2} \right )的,更新并查集还有个O\left(nlog_{n} \right )

查询的时候就先将u,v两点到其祖先中最近的选择点的距离上的贡献给暴力跑出来,在从选择的点一段一段的更新,最后到两者lca的那一段距离再暴力更新,当然,如果两者的路径间根本没有点被选择直接暴力跑就行了。这样查询的时间复杂度是O\left( m(\frac{n}{S}+\frac{n}{w}+S+log_{n})\right )的。好难看呀

总时间复杂度O\left((n+m)log_{n}+ \frac{n^2+nm}{S}+\frac{\frac{n^3}{S^2}+nm}{w}+mS \right )特难看。

可以发现,其实最优时S\sqrt{n}的样子,时间复杂度为O((n+m)\sqrt{n}+\frac{n^2+nm}{w})。不过实际数据检测,S还是可以取大点。

源码

用重链剖分的方式跑lca好像比倍增快些,不过笔者还是放二点倍增版本。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<bitset>
using namespace std;
#define MAXN 40005
typedef long long LL;
typedef pair<int,int> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,val[MAXN],b[MAXN],cnt,head[MAXN],tot;
int f[MAXN][22],mxd[MAXN],dep[MAXN];
int father[MAXN],sta[MAXN],stak,id[MAXN],idx;
int ltp[MAXN],ff[MAXN],las;
bitset<MAXN>bt[45][45],now;
struct edge{int to,nxt;}e[MAXN<<1];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
void dfs(int u,int fa){
	mxd[u]=dep[u]=dep[fa]+1;f[u][0]=father[u]=fa;
	for(int i=1;i<21;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		dfs(v,u);mxd[u]=max(mxd[u],mxd[v]);
	}
	if(mxd[u]-dep[u]>=1000)id[u]=++idx,mxd[u]=dep[u];
}
void dfs2(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		if(id[v]){
			int ip=id[sta[stak]],in=id[v];
			for(int j=v;j!=sta[stak];j=father[j])
				bt[ip][in].set(val[j]);
			now=bt[ip][in];
			for(int j=1;j<stak;j++){
				bitset<MAXN>&bs=bt[id[sta[j]]][in];
				bs=bt[id[sta[j]]][ip];bs|=now; 
			}
			ff[v]=sta[stak];sta[++stak]=v;
		}
		dfs2(v,u);if(id[v])stak--;
	}
} 
int lca(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	for(int i=20;i>=0;i--)if(dep[f[a][i]]>=dep[b])a=f[a][i];
	if(a==b)return a;
	for(int i=20;i>=0;i--)if(f[a][i]!=f[b][i])a=f[a][i],b=f[b][i];
	return f[a][0];
}
signed main(){
	read(n);read(m);
	for(int i=1;i<=n;i++)read(val[i]),b[++cnt]=val[i];
	sort(b+1,b+cnt+1);cnt=unique(b+1,b+cnt+1)-b-1;
	for(int i=1;i<=n;i++)val[i]=lower_bound(b+1,b+cnt+1,val[i])-b;
	for(int i=1;i<n;i++){
		int u,v;read(u);read(v);
		addEdge(u,v);addEdge(v,u);
	}
	dfs(1,0);if(!id[1])id[1]=++idx;
	sta[++stak]=1;dfs2(1,0);
	while(m--){
		int u,v;read(u);read(v);now.reset();u^=las;int u_v=lca(u,v);
		while(u!=u_v&&!id[u])now.set(val[u]),u=father[u];
		while(v!=u_v&&!id[v])now.set(val[v]),v=father[v];
		//printf("%d %d %d %d %d\n",u,v,id[u],id[v],u_v);
		if(u!=u_v){
			int pre=u;while(dep[ff[pre]]>=dep[u_v])pre=ff[pre];
			if(pre!=u)now|=bt[id[pre]][id[u]];
			while(pre!=u_v)now.set(val[pre]),pre=father[pre];
		}
		if(v!=u_v){
			int pre=v;while(dep[ff[pre]]>=dep[u_v])pre=ff[pre];
			if(pre!=v)now|=bt[id[pre]][id[v]];
			while(pre!=u_v)now.set(val[pre]),pre=father[pre];
		}
		now.set(val[u_v]);printf("%d\n",las=now.count());
	}
	return 0;
}

谢谢!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值