题面
题解
百年未有之写点分……
好久没写了,也当复习了一遍吧。
对于树上的一个扫描半径为 d d d 的在 u u u 节点的雷达,我们将其所能覆盖到的点的集合称作 “圆 ( u , d ) (u,d) (u,d)”。
那么题目就是询问有多少个点至少被 k k k 个给定的圆中的 k − 1 k-1 k−1 个圆的交集包含。
显然,对于两个圆 ( A , d a ) (A,d_a) (A,da) 和 ( B , d b ) (B,d_b) (B,db),我们容易得到它们的交:
-
若这两圆相离,那么它们的交为空集。
-
若这两圆中其中一个包含另一个,那么它们的交为被包含的那个圆。
-
若这两圆相交,那么它们的交仍为一个圆,只不过这个圆的圆心可能并不在点上而已:
所以我们考虑拆点:在每一条边上都新增一个虚点。这样就能保证题目给的两个圆的交集的圆心在一个点上了。但这样能保证大于等于 2 2 2 个圆的交集的圆心也一定在某个点上吗?
假设当前已经合并了若干个圆得到了圆 ( A , d a ) (A,d_a) (A,da) 和圆 ( B , d b ) (B,d_b) (B,db),满足 A A A、 B B B 都在点上。
我们将两圆合并,设两圆的交集为 ( M , d m ) (M,d_m) (M,dm)。设 A A A 到 M M M 的距离为 d i s a dis_a disa, B B B 到 M M M 的距离为 d i s b dis_b disb, A A A 到 B B B 的距离为 d i s dis dis,其中 d a , d b , d i s d_a,d_b,dis da,db,dis 都是已知的。那么我们要解的其实是这么一个方程:
{ d i s a + d i s b = d i s d a − d i s a = d b − d i s b \begin{cases} dis_a+dis_b=dis\\ d_a-dis_a=d_b-dis_b \end{cases} {disa+disb=disda−disa=db−disb
得到:
{ d i s a = d i s + d a − d b 2 d i s b = d i s + d b − d a 2 \begin{cases} dis_a=\dfrac{dis+d_a-d_b}{2}\\ dis_b=\dfrac{dis+d_b-d_a}{2} \end{cases} ⎩⎪⎨⎪⎧disa=2dis+da−dbdisb=2dis+db−da
那么 M M M 在点上等价于 d i s a dis_a disa 是整数,即 d i s + d a − d b dis+d_a-d_b dis+da−db 为偶数。我们不妨设置一个函数 onvir ( u ) \operatorname{onvir}(u) onvir(u) 表示 u u u 是否在我们拆边建出来的虚点上,那么显然 d i s dis dis 的奇偶性和 onvir ( A ) + onvir ( B ) \operatorname{onvir}(A)+\operatorname{onvir}(B) onvir(A)+onvir(B) 的奇偶性相同,那么 d i s + d a − d b dis+d_a-d_b dis+da−db 的奇偶性和 ( onvir ( A ) + d a ) + ( onvir ( B ) − d b ) (\operatorname{onvir}(A)+d_a)+(\operatorname{onvir}(B)-d_b) (onvir(A)+da)+(onvir(B)−db) 的奇偶性相同。
考虑归纳证明对于合并出来的圆 O O O, onvir ( O ) \operatorname{onvir}(O) onvir(O) 和 d o d_o do 奇偶性始终相同:
-
对于初始状态,圆 O O O 是题目给出的圆, onvir ( O ) = 0 \operatorname{onvir}(O)=0 onvir(O)=0, d o d_o do 为偶数。
-
对于某两个圆 A A A 和 B B B,如果它们都满足条件,那么对于 A , B A,B A,B 合并出来的新圆 M M M 来说:
{ onvir ( M ) ≡ onvir ( A ) + d i s a ( m o d 2 ) d m = d a − d i s a \begin{cases} \operatorname{onvir}(M)\equiv \operatorname{onvir}(A)+dis_a\pmod 2\\ d_m=d_a-dis_a \end{cases} {onvir(M)≡onvir(A)+disa(mod2)dm=da−disa
(这里借用了上面推导的式子)易知 onvir ( M ) \operatorname{onvir}(M) onvir(M) 和 d m d_m dm 奇偶性相同当且仅当 onvir ( A ) \operatorname{onvir}(A) onvir(A) 和 d a d_a da 奇偶性相同。
证毕。
所以 ( onvir ( A ) + d a ) + ( onvir ( B ) − d b ) (\operatorname{onvir}(A)+d_a)+(\operatorname{onvir}(B)-d_b) (onvir(A)+da)+(onvir(B)−db) 始终为偶数,那么 d i s a dis_a disa 始终为整数,那么合并出来的圆的圆心始终在点上。
-
现在我们可以在 O ( log n ) O(\log n) O(logn) 甚至 O ( n log n ) − O ( 1 ) O(n\log n)-O(1) O(nlogn)−O(1) 的时间内在线维护两个圆的交了。
然后 “至少被 k − 1 k-1 k−1 个圆包含” 可以容斥:设 a n s i ans_i ansi 表示被除了第 i i i 个圆以外的圆都包含的点数, a n s ans ans 表示被所有圆都包含的点数,那么答案即为 ∑ i = 1 k a n s i − k ⋅ a n s \sum\limits_{i=1}^kans_i-k\cdot ans i=1∑kansi−k⋅ans。现在考虑如何维护。
首先 “除了第 i i i 个圆以外的圆的交集” 可以用前后缀合并处理出来,接下来的问题就是求树上一个圆所包含的点数,直接点分治即可。
大概就是先预处理出当前子树的答案,再往上跳处理父亲的答案,注意除去这个子树的答案的重复贡献,然后再类似地往上跳。
可以结合线段树类似地分析时间复杂度(一层 O ( n ) O(n) O(n),一共 log n \log n logn 层)。
代码如下:
#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
*/