首页 > 其他分享 >【XSY3971】不难题(点分治)

【XSY3971】不难题(点分治)

时间:2022-10-31 07:33:05浏览次数:61  
标签:难题 XSY3971 int onvir 分治 fa size operatorname dis

题面

不难题

题解

百年未有之写点分……

好久没写了,也当复习了一遍吧。

对于树上的一个扫描半径为 \(d\) 的在 \(u\) 节点的雷达,我们将其所能覆盖到的点的集合称作 “圆 \((u,d)\)”。

那么题目就是询问有多少个点至少被 \(k\) 个给定的圆中的 \(k-1\) 个圆的交集包含。

显然,对于两个圆 \((A,d_a)\) 和 \((B,d_b)\),我们容易得到它们的交:

  • 若这两圆相离,那么它们的交为空集。

  • 若这两圆中其中一个包含另一个,那么它们的交为被包含的那个圆。

  • 若这两圆相交,那么它们的交仍为一个圆,只不过这个圆的圆心可能并不在点上而已:

    在这里插入图片描述

    所以我们考虑拆点:在每一条边上都新增一个虚点。这样就能保证题目给的两个圆的交集的圆心在一个点上了。但这样能保证大于等于 \(2\) 个圆的交集的圆心也一定在某个点上吗?

    假设当前已经合并了若干个圆得到了圆 \((A,d_a)\) 和圆 \((B,d_b)\),满足 \(A\)、\(B\) 都在点上。

    我们将两圆合并,设两圆的交集为 \((M,d_m)\)。设 \(A\) 到 \(M\) 的距离为 \(dis_a\),\(B\) 到 \(M\) 的距离为 \(dis_b\),\(A\) 到 \(B\) 的距离为 \(dis\),其中 \(d_a,d_b,dis\) 都是已知的。那么我们要解的其实是这么一个方程:

    \[\begin{cases} dis_a+dis_b=dis\\ d_a-dis_a=d_b-dis_b \end{cases} \]

    得到:

    \[\begin{cases} dis_a=\dfrac{dis+d_a-d_b}{2}\\ dis_b=\dfrac{dis+d_b-d_a}{2} \end{cases} \]

    那么 \(M\) 在点上等价于 \(dis_a\) 是整数,即 \(dis+d_a-d_b\) 为偶数。

    我们不妨设置一个函数 \(\operatorname{onvir}(u)\) 表示 \(u\) 是否在我们拆边建出来的虚点上,那么显然 \(dis\) 的奇偶性和 \(\operatorname{onvir}(A)+\operatorname{onvir}(B)\) 的奇偶性相同,那么 \(dis+d_a-d_b\) 的奇偶性和 \((\operatorname{onvir}(A)+d_a)+(\operatorname{onvir}(B)-d_b)\) 的奇偶性相同。

    考虑归纳证明对于合并出来的圆 \(O\),\(\operatorname{onvir}(O)\) 和 \(d_o\) 奇偶性始终相同:

    • 对于初始状态,圆 \(O\) 是题目给出的圆,\(\operatorname{onvir}(O)=0\),\(d_o\) 为偶数。

    • 对于某两个圆 \(A\) 和 \(B\),如果它们都满足条件,那么对于 \(A,B\) 合并出来的新圆 \(M\) 来说:

      \[\begin{cases} \operatorname{onvir}(M)\equiv \operatorname{onvir}(A)+dis_a\pmod 2\\ d_m=d_a-dis_a \end{cases} \]

      (这里借用了上面推导的式子)

      易知 \(\operatorname{onvir}(M)\) 和 \(d_m\) 奇偶性相同当且仅当 \(\operatorname{onvir}(A)\) 和 \(d_a\) 奇偶性相同。

      证毕。

    所以 \((\operatorname{onvir}(A)+d_a)+(\operatorname{onvir}(B)-d_b)\) 始终为偶数,那么 \(dis_a\) 始终为整数,那么合并出来的圆的圆心始终在点上。

现在我们可以在 \(O(\log n)\) 甚至 \(O(n\log n)-O(1)\) 的时间内在线维护两个圆的交了。

然后 “至少被 \(k-1\) 个圆包含” 可以容斥:设 \(ans_i\) 表示被除了第 \(i\) 个圆以外的圆都包含的点数,\(ans\) 表示被所有圆都包含的点数,那么答案即为 \(\sum\limits_{i=1}^kans_i-k\cdot ans\)。现在考虑如何维护。

首先 “除了第 \(i\) 个圆以外的圆的交集” 可以用前后缀合并处理出来,接下来的问题就是求树上一个圆所包含的点数,直接点分治即可。

大概就是先预处理出当前子树的答案,再往上跳处理父亲的答案,注意除去这个子树的答案的重复贡献,然后再类似地往上跳。

可以结合线段树类似地分析时间复杂度(一层 \(O(n)\),一共 \(\log n\) 层)。

代码如下:

#include<bits/stdc++.h>

#define LN 18
#define N 200010
#define K 300010
#define INF 0x7fffffff

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,q;
int cnt,head[N],nxt[N<<1],to[N<<1];

void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

namespace LCA
{
	int pow2[LN];
	int d[N],fa[N][LN];
	void dfs(int u)
	{
		for(int i=1;i<=17;i++)
			fa[u][i]=fa[fa[u][i-1]][i-1];
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa[u][0]) continue;
			fa[v][0]=u;
			d[v]=d[u]+1;
			dfs(v);
		}
	}
	int getlca(int a,int b)
	{
		if(d[a]<d[b]) swap(a,b);
		for(int i=17;i>=0;i--)
			if(d[fa[a][i]]>=d[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];
	}
	int getdis(int a,int b,int lca=-1)
	{
		if(lca==-1) lca=getlca(a,b);
		return d[a]+d[b]-2*d[lca];
	}
	int jump(int u,int d)
	{
		for(int i=17;i>=0;i--)
		{
			if(d>=pow2[i])
			{
				u=fa[u][i];
				d-=pow2[i];
			}
		}
		return u;
	}
	void init()
	{
		pow2[0]=1;
		for(int i=1;i<=17;i++)
			pow2[i]=pow2[i-1]<<1;
		d[1]=1;
		dfs(1);
	}
}

namespace Tree
{
	int nn,rt,size[N],maxn[N];
	int nt,num[N],tim[N];
	bool vis[N];
	int fa[N];
	vector<int>v1[N],v2[N];
	void getsize(int u,int fa)
	{
		size[u]=1;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa||vis[v]) continue;
			getsize(v,u);
			size[u]+=size[v];
		}
	}
	void getroot(int u,int fa)
	{
		size[u]=1,maxn[u]=0;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa||vis[v]) continue;
			getroot(v,u);
			size[u]+=size[v];
			maxn[u]=max(maxn[u],size[v]);
		}
		maxn[u]=max(maxn[u],nn-size[u]);
		if(maxn[u]<maxn[rt]) rt=u;
	}
	void getdis(int u,int fa,int dis)
	{
		if(tim[dis]!=nt)
		{
			tim[dis]=nt;
			num[dis]=0;
		}
		if(u<=n) num[dis]++;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa||vis[v]) continue;
			getdis(v,u,dis+1);
		}
	}
	void turn(vector<int> &v)
	{
		int now=0;
		for(int i=0;tim[i]==nt;i++)
		{
			now+=num[i];
			v.push_back(now);
		}
	}
	void build(int u)
	{
		nt++,getdis(u,0,0);
		turn(v1[u]);
		getsize(u,0);
		vis[u]=1;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(vis[v]) continue;
			rt=0,nn=size[v];
			getroot(v,0);
			fa[rt]=u;
			nt++,getdis(v,0,0);
			turn(v2[rt]);
			build(rt);
		}
	}
	void init()
	{
		maxn[0]=INF;
		rt=0,nn=(n<<1);
		getroot(1,0);
		build(rt);
	}
	int get(vector<int> &v,int d)
	{
		if(d<0||!v.size()) return 0;
		return v[min(d,(int)v.size()-1)];
	}
	int query(int u,int d)
	{
		if(d<0) return 0;
		int st=u,dd=d,ans=0;
		while(u)
		{
			ans+=get(v1[u],d);
			d=dd-LCA::getdis(fa[u],st);
			ans-=get(v2[u],d-1);
			u=fa[u];
		}
		return ans;
	}
}

struct data
{
	int u,d;
	data(){};
	data(int a,int b){u=a,d=b;}
}p[K],pre[K],suf[K];

data operator + (data a,data b)
{
	if(a.d<0||b.d<0) return data(114514,-1);
	int lca=LCA::getlca(a.u,b.u);
	int dis=LCA::getdis(a.u,b.u,lca);
	if(a.d>dis+b.d) return b;
	if(b.d>dis+a.d) return a;
	int disa=(dis+a.d-b.d)/2,disb=(dis+b.d-a.d)/2;
	int mid;
	if(disa<=LCA::getdis(a.u,lca,lca)) mid=LCA::jump(a.u,disa);
	else mid=LCA::jump(b.u,disb);
	return data(mid,a.d-disa);
}

int query(data a)
{
	return Tree::query(a.u,a.d);
}

int main()
{
	n=read();
	int nn=(n<<1)-1;
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		adde(u,n+i),adde(n+i,u);
		adde(v,n+i),adde(n+i,v);
	}
	LCA::init();
	Tree::init();
	q=read();
	while(q--)
	{
		int k=read();
		for(int i=1;i<=k;i++)
			p[i].u=read(),p[i].d=(read()<<1);
		pre[0]=suf[k+1]=data(1,nn);
		for(int i=1;i<=k;i++)
			pre[i]=pre[i-1]+p[i];
		for(int i=k;i>=1;i--)
			suf[i]=suf[i+1]+p[i];
		int ans=0;
		for(int i=1;i<=k;i++)
			ans+=query(pre[i-1]+suf[i+1]);
		ans-=(k-1)*query(pre[k]);
		printf("%d\n",ans);
	}
	return 0;
}
/*
10
1 3
6 4
9 8
1 8
3 4
2 8
10 3
4 5
8 7
2
3
8 1
3 1
3 2
2
7 3
6 0
*/
/*
5
1 2
1 3
3 4
3 5
114514
2
1 1
5 1
*/

标签:难题,XSY3971,int,onvir,分治,fa,size,operatorname,dis
From: https://www.cnblogs.com/ez-lcw/p/16842952.html

相关文章