bzoj 3572 [Hnoi2014]世界树(虚树+DP)

3572: [Hnoi2014]世界树

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 645  Solved: 362
[Submit][Status][Discuss]

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

6 1

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

10

1 1 3 5

4 1 3 1 1

HINT

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

Source

【思路】

虚树+Dfs序+DP

构造一棵虚树,所谓虚树即包含了给定点,并收缩了不分叉边的连通子图(顺的kuangbin的定义) 虚树的目的应该和scc缩点的目的一致吧。

基于构树的dfs序,两遍for可以求出每个点被谁管辖,然后再虚树上进行节点的分配。

详见代码。

【代码】

  1 #include<map>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<vector>
  5 #include<algorithm>
  6 #define mp(x,y) make_pair(x,y)
  7 using namespace std;
  8
  9 const int N = 300000+10;
 10 const int INF = 1e9+1e9;
 11 const int maxd = 20;
 12
 13 vector<int> G[N];
 14 int d[N],sz[N],dfn[N],f[N][maxd];
 15 int n,q,dfsc;
 16
 17 int cmp(const int &lhs,const int &rhs) { return dfn[lhs]<dfn[rhs];
 18 }
 19 void dfs(int u,int fa) {
 20     dfn[u]=++dfsc;
 21     sz[u]=1;
 22     for(int v,i=0;i<G[u].size();i++) if((v=G[u][i])!=fa) {
 23         f[v][0]=u;
 24         for(int j=1;j<maxd;j++) f[v][j]=f[f[v][j-1]][j-1];
 25         d[v]=d[u]+1;
 26         dfs(v,u);
 27         sz[u]+=sz[v];
 28     }
 29 }
 30 int LCA(int u,int v) {
 31     if(d[v]>d[u]) swap(u,v);
 32     for(int i=maxd-1;i>=0;i--)
 33         if(d[f[u][i]]>=d[v]) u=f[u][i];
 34     if(u==v) return u;
 35     for(int i=maxd-1;i>=0;i--)
 36         if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
 37     return f[u][0];
 38 }
 39 int find(int u,int dep) {
 40     for(int i=maxd-1;i>=0;i--)
 41         if(d[f[u][i]]>=dep) u=f[u][i];
 42     return u;
 43 }
 44
 45 void solve() {
 46     int m,tot=0,top=0;
 47     //static 可以声明函数内大数组
 48     static int h[N],t[N],mem[N],st[N],val[N],father[N],w[N],ans[N];
 49     static pair<int,int> g[N];
 50     scanf("%d",&m);
 51     for(int i=1;i<=m;i++) {
 52         scanf("%d",&h[i]);
 53         mem[i]=h[i];
 54         t[++tot]=h[i];
 55         g[h[i]]=mp(0,h[i]);
 56         ans[h[i]]=0;
 57     }
 58     sort(h+1,h+m+1,cmp);
 59     for(int i=1;i<=m;i++) {
 60         if(!top) father[st[++top]=h[i]]=0;
 61         else {
 62             int p=h[i],lca=LCA(p,st[top]);
 63             for(; d[lca]<d[st[top]];--top)
 64                 if(d[st[top-1]]<=d[lca])
 65                     father[st[top]]=lca;
 66             if(st[top]!=lca) {
 67                 father[lca]=st[top];
 68                 t[++tot]=lca;
 69                 g[lca]=mp(INF,0);
 70                 st[++top]=lca;
 71             }
 72             st[++top]=p;
 73             father[p]=lca;
 74         }
 75     }
 76     sort(t+1,t+tot+1,cmp);
 77     for(int i=1;i<=tot;i++) {
 78         int p=t[i];
 79         val[p]=sz[p];
 80         if(i>1) w[p]=d[p]-d[father[p]];        //路径长
 81     }
 82     //基于dfs序进行两次for 上下不同递推方向比较得最近点
 83     for(int i=tot;i>1;i--) {
 84         int p=t[i] , fa=father[p];
 85         g[fa]=min(mp(g[p].first+w[p],g[p].second),g[fa]);     //g存储到p最近询问点的长度与编号
 86     }
 87     for(int i=2;i<=tot;i++) {
 88         int p=t[i] , fa=father[p];
 89         g[p]=min(mp(g[fa].first+w[p],g[fa].second),g[p]);
 90     }
 91     for(int i=1;i<=tot;i++) {
 92         int p=t[i],fa=father[p];
 93         if(i==1) ans[g[p].second]+=n-sz[p];        //管辖虚树根节点的 += 原树中虚根上方的节点数
 94         else {
 95             int x=find(p,d[fa]+1),sum=sz[x]-sz[p];
 96             val[fa]-=sz[x];
 97             if(g[fa].second==g[p].second) ans[g[p].second]+=sum;    // fa与p同是lca式而非询问点
 98             else {
 99                 int mid=d[p]-((g[fa].first+g[p].first+w[p])/2-g[p].first);    //中点距
100                 if((g[fa].first+g[p].first+w[p])%2==0 && g[p].second>g[fa].second) ++mid;    //根据序号判断偏向于fa还是p
101                 int y=sz[find(p,mid)]-sz[p];
102                 ans[g[p].second] += y;            //fa与p分配管辖点
103                 ans[g[fa].second] += sum-y;
104             }
105         }
106     }
107     for(int i=1;i<=tot;i++)
108         ans[g[t[i]].second] += val[t[i]];            //剩余未分配点
109     for(int i=1;i<=m;i++) printf("%d ",ans[mem[i]]); puts("");
110 }
111
112 int main() {
113     scanf("%d",&n);
114     int u,v;
115     for(int i=0;i<n-1;i++) {
116         scanf("%d%d",&u,&v);
117         G[u].push_back(v) , G[v].push_back(u);
118     }
119     d[1]=1; dfs(1,-1);
120     scanf("%d",&q);
121     while(q--) solve();
122     return 0;
123 }
时间: 2024-08-24 21:47:07

bzoj 3572 [Hnoi2014]世界树(虚树+DP)的相关文章

BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]

传送门 题意: 一棵树,多次询问,给出$m$个点,求有几个点到给定点最近 写了一晚上... 当然要建虚树了,但是怎么$DP$啊 大爷题解传送门 我们先求出到虚树上某个点最近的关键点 然后枚举所有的边$(f,x)$,讨论一下边上的点的子树应该靠谁更近 倍增求出分界点 注意有些没出现在虚树上的子树 注意讨论的时候只讨论链上的不包括端点,否则$f$的子树会被贡献多次 学到的一些$trick:$ 1.$pair$的妙用 2.不需要建出虚树只要求虚树的$dfs$序(拓扑序)和$fa$就可以$DP$了 注意

bzoj 3572: [Hnoi2014]世界树

再次跪虚树(DP)(两遍DP挺有意思的..) (这个题的情况,,跪) %%%http://hzwer.com/6804.html 1 #include <bits/stdc++.h> 2 #define LL long long 3 #define N 300005 4 using namespace std; 5 inline int ra() 6 { 7 int x=0,f=1; char ch=getchar(); 8 while (ch<'0' || ch>'9') {if

[HNOI2014][BZOJ3572] 世界树|虚树|树上倍增LCA|树型dp|dfs序

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 555  Solved: 319[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.    世界树的形态可以用一个数学模型来描述:世界树中有n个种

BZOJ 3572 世界树(虚树)

http://www.lydsy.com/JudgeOnline/problem.php?id=3572 思路:建立虚树,然后可以发现,每条边不是同归属于一端,那就是切开,一半给上面,一半给下面. 1 #include<algorithm> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream> 6 #define N 300005 7 int t

【BZOJ-3572】世界树 虚树 + 树形DP

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1084  Solved: 611[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.世界树的形态可以用一个数学模型来描述:世界树中有n个种族,种

【BZOJ3572】【Hnoi2014】世界树 虚树

链接: #include <stdio.h> int main() { puts("转载请注明出处[辗转山河弋流歌 by 空灰冰魂]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/46506883"); } 题解: 首先构建虚树,然后在虚树上DP. 过程很简单. 先找出每个虚树节点 i 旁边最近的询问节点 neari (因为有一些lca也被加入了虚树所以虚树节点不全是询问节点,呃怕你们不懂,但其实

CodeForces - 613D:Kingdom and its Cities(虚树+DP)

Meanwhile, the kingdom of K is getting ready for the marriage of the King's daughter. However, in order not to lose face in front of the relatives, the King should first finish reforms in his kingdom. As the King can not wait for his daughter's marri

luogu3233 世界树 (虚树)

反正肯定要建虚树,考虑建完之后怎么做 先随便dp一下算出来距离某点最近的询问点mi[x](因为有的虚树上的点它不是询问点嘛) 那我们对于某条链x到fa[x]上的非虚树上的点(包括他们的非虚树上的孩子),要么把它分给mi[x],要么分给mi[fa[x]] 我找到这个中间点以后,在原树上倍增跳过去,算他的size 这个分法是以$\frac{len[mi[x]]+len[x]+len[mi[fa[x]]]}{2}$再加加减减一些细节决定的(len[x]表示x到fa[x]的链的长度) 除此之外,每个在虚

Codeforces 1111E Tree 虚树 + dp

直接把 r 加进去建虚树, 考虑虚树上的dp, 我们考虑虚树的dfs序的顺序dp过去. dp[ i ][ j ]  表示到 i 这个点为止, 分成 j 组有多少种合法方案. dp[ i ][ j ] = dp[ i - 1 ][ j ] * (j - have[ i ])  + dp[ i - 1 ][ j - 1 ], have[ i ] 表示 i 的祖先中有多少个在a中出现. #include<bits/stdc++.h> using namespace std; const int N