BZOJ 3572 世界树

时间:2022-03-03 14:30:50

Description

世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界。在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息、持续运转的根本基石。
    世界树的形态可以用一个数学模型来描述:世界树中有n个种族,种族的编号分别从1到n,分别生活在编号为1到n的聚居地上,种族的编号与其聚居地的编号相同。有的聚居地之间有双向的道路相连,道路的长度为1。保证连接的方式会形成一棵树结构,即所有的聚居地之间可以互相到达,并且不会出现环。定义两个聚居地之间的距离为连接他们的道路的长度;例如,若聚居地a和b之间有道路,b和c之间有道路,因为每条道路长度为1而且又不可能出现环,所卧a与c之间的距离为2。
    出于对公平的考虑,第i年,世界树的国王需要授权m[i]个种族的聚居地为临时议事处。对于某个种族x(x为种族的编号),如果距离该种族最近的临时议事处为y(y为议事处所在聚居地的编号),则种族x将接受y议事处的管辖(如果有多个临时议事处到该聚居地的距离一样,则y为其中编号最小的临时议事处)。
    现在国王想知道,在q年的时间里,每一年完成授权后,当年每个临时议事处将会管理多少个种族(议事处所在的聚居地也将接受该议事处管理)。 现在这个任务交给了以智慧著称的灵长类的你:程序猿。请帮国王完成这个任务吧。

Input

第一行为一个正整数n,表示世界树中种族的个数。
    接下来n-l行,每行两个正整数x,y,表示x聚居地与y聚居地之间有一条长度为1的双
向道路。接下来一行为一个正整数q,表示国王询问的年数。
    接下来q块,每块两行:
    第i块的第一行为1个正整数m[i],表示第i年授权的临时议事处的个数。
    第i块的第二行为m[i]个正整数h[l]、h[2]、…、h[m[i]],表示被授权为临时议事处的聚居地编号(保证互不相同)。

Output

输出包含q行,第i行为m[i]个整数,该行的第j(j=1,2…,,m[i])个数表示第i年被授权的聚居地h[j]的临时议事处管理的种族个数。

Sample Input

10
2 1
3 2
4 3
5 4
6 1
7 3
8 3
9 4
10 1
5
2
61
5
2 7 3 6 9
1
8
4
8 7 10 3
5
2 9 3 5 8

Sample Output

1 9
3 1 4 1 1
1 0
1 1 3 5
4 1 3 1 1

HINT

N<=300000, q<=300000,m[1]+m[2]+…+m[q]<=300000

Source

这题挺神的,让我感觉到dp的博大精深。
这题还是要利用到虚树,构建我也就不再赘述——BZOJ 2286 消耗战
但是我们要考虑虚树构建完后我们要如何做dp。
对于虚树上每个点i,我们可以记录其在虚树上的父亲fa是谁,利用这一点来dp。near[i]=min(near[i],near[fa]+dis(i,fa)),near[fa]=min(near[fa],near[i]+dis(i,fa)),这在两个循环中完成。代码实现其实可以写的很巧妙,比如ydc神犇写的一个pair,这样既可以进行双关键字比较,又可以知道距离每个虚树上点的最近点是哪个。
然后,就是我觉得最神奇的地方——算贡献了。分几种情况来讨论:
1.首先,虚树中没有父亲的点p一定是最高的点,有大小为n-size[p]的点与他共用最近点。
2.每个虚树上的点和他没在虚树上的子树肯定会共用一个最近点这个我们只要用其总的size减去在虚树中的size最后统计答案即可。
3.对于虚树某一对父子i,fa,若他们共用一对最近点,则他们在树上的链上点也一定共用这个最近点。
4.若并不共用最近点,肯定有某一段与fa共用最近点,另一段与i共用最近点。因此我们需要找到这个分界点p。由于p到i最近点的距离与p到fa最近点的距离尽可能的接近,所以有dis(i,p)=(near[fa]-near[i]+dis(i,fa))/2,这样得出来的点是最接近的,并且考虑了不能整除的情况,但是仍然需要特判一些情况——p到i与p到fa的距离一样近,我们就需要比较他们点的大小,适当的调整p的位置。
最后贴一份自己的代码,大部分都是照着ydc的扒的,自己绝对写不出来。
 #include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std; #define maxn 300010
int side[maxn],next[maxn*],f[maxn][],tmp[maxn],stack[maxn],DFN;
int toit[maxn*],dfn[maxn],dep[maxn],ask[maxn],size[maxn],tree[maxn],cnt,n;
int ans[maxn],father[maxn],val[maxn],in[maxn]; pair <int,int> near[maxn]; inline bool cmp(int a,int b) { return dfn[a] < dfn[b]; } inline void add(int a,int b) { next[++cnt] = side[a]; side[a] = cnt; toit[cnt] = b; } inline void ins(int a,int b) { add(a,b); add(b,a); } inline void dfs(int now)
{
dfn[now] = ++DFN;
for (int i = ;( << i)<=dep[now];++i) f[now][i] = f[f[now][i-]][i-];
for (int i = side[now];i;i = next[i])
if (toit[i]!=f[now][])
{
f[toit[i]][] = now; dep[toit[i]] = dep[now]+;
dfs(toit[i]);size[now] += size[toit[i]]; }
size[now]++;
} inline void jump(int &a,int step)
{
for (int i = ;step;step >>= ,++i)
if (step&) a = f[a][i];
} inline int lca(int a,int b)
{
if (dep[a] < dep[b]) swap(a,b);
jump(a,dep[a]-dep[b]);
if (a == b) return a;
for (int i = ;i >= ;)
{
if (f[a][i] != f[b][i]) a = f[a][i],b = f[b][i],++i;
else --i;
}
return f[a][];
} inline int find(int now,int d)
{
for (int i = ;i>=&&dep[now]>d;)
{
if (dep[f[now][i]] >= d) now = f[now][i],++i;
else --i;
}
return now;
} inline void solve()
{
int k; scanf("%d ",&k);
for (int i = ;i <= k;++i)
{
scanf("%d",ask+i),tree[i] = tmp[i] = ask[i];
near[ask[i]] = make_pair(,ask[i]); ans[ask[i]] = ;
}
sort(ask+,ask+k+,cmp);
int top = ,tot = k;
for (int i = ;i <= k;++i)
{
int p = ask[i];
if (!top) stack[++top] = p,father[p] = ;
else
{
int x = lca(stack[top],p);
father[p] = x;
while (top&&dep[stack[top]] > dep[x])
{
if (dep[stack[top-]] <= dep[x])
father[stack[top]] = x;
--top;
}
if (stack[top] != x)
{
father[x] = stack[top]; tree[++tot] = x;
stack[++top] = x; near[x] = make_pair(<<,);
}
stack[++top] = p;
}
}
sort(tree+,tree+tot+,cmp);
for (int i = ;i <= tot;++i)
{
int p = tree[i]; val[p] = size[p];
if (i > ) in[p] = dep[p]-dep[father[p]];
}
for (int i = tot;i > ;--i)
{
int p = tree[i],fa = father[p];
near[fa] = min(near[fa],make_pair(near[p].first+in[p],near[p].second));
}
for (int i = ;i <= tot;++i)
{
int p = tree[i],fa = father[p];
near[p] = min(near[p],make_pair(near[fa].first+in[p],near[fa].second));
}
for (int i = ;i <= tot;++i)
{
int p = tree[i],fa = father[p],sum = size[find(p,dep[fa]+)]-size[p];
if (fa == ) ans[near[p].second] += n-size[p];
else
{
val[fa] -= sum+size[p];
if (near[p].second == near[fa].second)
ans[near[p].second] += sum;
else
{
int dis = (dep[p]-dep[fa]-near[p].first+near[fa].first)>>;
if (dis+near[p].first==near[fa].first+dep[p]-dep[fa]-dis&&near[fa].second<near[p].second)
--dis;
int x = find(p,dep[p]-dis);
ans[near[p].second] += size[x]-size[p];
ans[near[fa].second] += sum+size[p]-size[x];
}
}
}
for (int i = ;i <= tot;++i)
ans[near[tree[i]].second] += val[tree[i]];
for (int i = ;i <= k;++i) printf("%d ",ans[tmp[i]]);
puts("");
} int main()
{
freopen("3572.in","r",stdin);
freopen("3572.out","w",stdout);
scanf("%d",&n);
for (int i = ;i < n;++i) { int x,y; scanf("%d %d",&x,&y); ins(x,y); }
dfs();
int T; scanf("%d",&T);
while (T--) solve();
return ;
}