Harry and Christmas tree
Time Limit: 5000/2500 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 70 Accepted Submission(s): 3
问题描述
圣诞节的夜晚,哈利得到一棵圣诞树。这棵树由n个节点组成,以1号节点为根。树上的每个节点有一些不同颜色的礼物,于是哈利开始统计树上的礼物。哈利每次统计一个礼物,会记录一个(a, b),a表示这个礼物所在的节点,b表示这个礼物的颜色。统计完之后,哈利想知道,每个节点所包含的子树下,有几种不同颜色的礼物。
输入描述
多组输入数据 每组数据第一行由n m组成表示树的大小,以及礼物的个数1≤n≤50000,1≤m≤500000 接下来n-1行,每行两个数a b表示节点(a, b)之间有一条边1≤a,b≤n,a≠b 接下来m行,每行两个数a b表示在a节点上有一个颜色为b的礼物1≤a≤n,1≤b≤100000
输出描述
每组数据输出一行n整数,第i个数表示i节点所包含的子树下有几种不同颜色的礼物。
输入样例
5 3 3 1 4 1 2 3 5 1 1 9 4 6 4 6
输出样例
2 0 0 1 0 提示: 尽可能的让你的程序跑得快一点
读完题的时候没什么思路,暴力一看过不去,在合并子树信息的时候想到要为每种颜色判重。。然后,就不知所措了。。比赛结束后看到了题解,发现真是太神了。
①怎么判重?不判重!为每种颜色单独处理:设我们已经处理好了i个节点,那么我们在处理第i+1个节点的时候,只需要在第i+1个节点上打上1的标记,在第i+1个节点与前i个节点的LCA处打上-1的标记,最后将标记累加即可!
②LCA该怎么求?求出i对节点的LCA?No!
神奇的DFS序!
DFS有一个很好的特性,它在处理一棵子树时,在处理完这棵子树之前不会处理其他的子树;这将作为我们下面证明的引理。
定理:记节点i,j的LCA为(i,j),记节点x的深度为Dx;若将颜色1对应的n个节点以DFS序排序为{a1,a2,a3,...,an},则对任意i必有D(a1,ai)<=D(a2,ai)<=D(a3,ai)<=...<=D(ai-1,ai)(即(ai-1,ai)即为ai与前i-1个节点的LCA,所以我们只需要在(ai-1,ai)上打上-1的标记即可)。
证明:
显然,i∈[2,n]∩N。
①i=2,则只存在(a1,a2)一项,定理成立。
②i>2,若存在j∈[1,i-2]∩N,使得D(aj,ai)>D(aj+1,ai).
则(aj,ai)在以(aj+1,ai)为根的子树中,即aj在以(aj+1,ai)为根的子树中;
且(aj+1,ai)不在以(aj,ai)为根的子树中,
因为若(aj+1,ai)在以(aj,ai)为根的子树中,则D(aj+1,ai)>=D(aj,ai).
即aj+1不在以(aj,ai)为根的子树中。
设以(ai,aj)为根的子树为T,
则DFS访问T时必已经访问过aj+1,或没有访问aj+1。
又∵aj∈T,ai∈T。
若设节点x的DFS序为P(x),
则P(aj+1)>P(ai)>P(aj)或P(aj+1)<P(aj)<P(ai)
但由题中所设,P(aj)<P(aj+1)<P(ai),皆与上述两种情况矛盾。
故不存在j∈[1,i-2]∩N,使得D(aj,ai)>D(aj+1,ai).
即原命题成立。
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> vector<int> nodegift[50001],sorted[100001]; int fa[50001],n[100001],p[50001],s[100001],ans[50001]; inline void Sort(int x,int ftr){ int i; for(i=nodegift[x].size();i--;) sorted[nodegift[x][i]].push_back(x); for(i=p[x];i;i=n[i]) if(s[i]!=ftr) Sort(s[i],x); } inline int find(int x){ return fa[x]!=fa[fa[x]]?fa[x]=find(fa[x]):fa[x]; } vector<int> query[50001]; inline void Tarjan(int x,int ftr){ int i; fa[x]=x; for(i=p[x];i;i=n[i]) if(s[i]!=ftr) Tarjan(s[i],x); for(i=query[x].size();i--;) --ans[find(query[x][i])]; fa[x]=ftr; ans[ftr]+=ans[x]; } int main(){ int i,N,m,j,a,b; while(~scanf("%d%d",&N,&m)){ /*----Initialize----*/ memset(p,0,sizeof(int)*(N+1)); memset(ans,0,sizeof(int)*(N+1)); for(i=N;i;--i){ vector<int>().swap(nodegift[i]); vector<int>().swap(query[i]); } /*------Input-------*/ for(i=N;--i;){ scanf("%d%d",&a,&b); n[i]=p[a],p[a]=i,s[i]=b; n[i+N]=p[b],p[b]=i+N,s[i+N]=a; } while(m--){ scanf("%d%d",&a,&b); nodegift[a].push_back(b); } for(i=100000;i;--i)vector<int>().swap(sorted[i]); /*-Sort for a Order-*/ Sort(1,0); for(i=100000;i;--i){ for(j=sorted[i].size();j--;) ++ans[sorted[i][j]]; for(j=sorted[i].size()-1;j>0;--j) query[sorted[i][j]].push_back(sorted[i][j-1]); } /*-Tarjan for anses-*/ Tarjan(1,0); /*-------Output-----*/ for(i=1;i<N;++i)printf("%d ",ans[i]); printf("%d\n",ans[N]); } }
代码之后:
一、
这道题因为先求了一遍DFS序,所以无论是Tarjan特别好写,但我第一遍还是写残了:
③我在清sorted数组的时候忘记了sorted的意义,一不小心把它和其余的数组一起从1~N清了,这显然是不正确的,以后写代码的时候一定要注意这一点:头脑清醒,分清自己给变量设置的意义。
更加错误的是我竟然连数据也没编对拍也没写就交上去了,这实在是兵家之大忌;以后一定要记住了!!!!写完代码之后,不管是多简单的题——第一件事一定是编数据;如果发现能写个暴力什么的话,就写个对拍。
二、借着这道题顺便学了一下DFS序,发现还是很神的,下面是一些学习笔记:
④DFS序可以用来维护树中两点距离,可以用来维护子树权和:
因为在DFS序中,一个子树一定是连成一片的,所以子树求和就化为了区间求和问题;
若在进入节点时写下节点权值和,退出节点时写下节点节点权值和的相反数,则节点和某一以其为根的子树中的节点的距离就可以用它们之间的区间和来表示了,因为不在路径上的点一定在进入之后会被退出,而在路径上的点在进入之后一定不会被退出;这与LCA转RMQ其实有异曲同工之妙。
⑤对于求树的直径
一个强大的贪心:距离树中任意一节点最远的点一定是树的直径的某一端点;
一个靠谱的DP:树的直径一定是由一个点向下的一条最短路和次短路接成。