题面
题解
百年未有之写点分……
好久没写了,也当复习了一遍吧。
对于树上的一个扫描半径为 \(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