树上差分
一个写的很好的博客:https://blog.csdn.net/liuzibujian/article/details/81346595
差分真神奇...还可以跑到树上去。之前其实做过两个这种题,但是今天见到了一道神题“天天爱跑步”,发现树上差分远没有我想的那么简单。
天天爱跑步:https://www.luogu.org/problemnew/show/P1600
题意概述:给出一棵n个点的树以及树上的m条路径,每个点带有点权,求对于每个点,有多少条路径经过这个点时所走的长度恰好等于点权。
这题的数据范围真有趣,专门用于写部分分。
1 # include <cstdio> 2 # include <iostream> 3 # include <vector> 4 # include <cstring> 5 # define R register int 6 7 using namespace std; 8 9 const int maxn=300000; 10 int n,m,x,y,h; 11 int w[maxn],c[maxn]; 12 int s[maxn],t[maxn],vis[maxn],firs[maxn],dep[maxn],L[maxn]; 13 int F[maxn][20]; 14 vector <int> en[maxn]; 15 int num[maxn]; 16 struct edge 17 { 18 int too,nex; 19 }g[maxn<<1]; 20 21 void add (int x,int y) 22 { 23 g[++h].too=y; 24 g[h].nex=firs[x]; 25 firs[x]=h; 26 } 27 28 void st () 29 { 30 for (int i=1;i<=m;++i) 31 if(w[ s[i] ]==0) c[ s[i] ]++; 32 } 33 34 void dfs (int x) 35 { 36 int j; 37 for (int i=firs[x];i;i=g[i].nex) 38 { 39 j=g[i].too; 40 if(dep[j]) continue; 41 dep[j]=dep[x]+1; 42 F[j][0]=x; 43 for (int i=1;i<=19;++i) 44 F[j][i]=F[ F[j][i-1] ][i-1]; 45 dfs(j); 46 } 47 } 48 49 int lca (int x,int y) 50 { 51 if(dep[x]>dep[y]) swap(x,y); 52 for (int i=19;i>=0;--i) 53 if(dep[x]<=dep[y]-(1<<i)) 54 y=F[y][i]; 55 if(x==y) return x; 56 for (int i=19;i>=0;--i) 57 { 58 if(F[x][i]==F[y][i]) continue; 59 x=F[x][i]; 60 y=F[y][i]; 61 } 62 return F[x][0]; 63 } 64 65 void link() 66 { 67 int siz; 68 memset(vis,0,sizeof(vis)); 69 memset(num,0,sizeof(num)); 70 for (int i=1;i<=m;++i) 71 if(s[i]<=t[i]) en[ t[i] ].push_back(s[i]),vis[ s[i] ]++; 72 for (int i=1;i<=n;++i) 73 { 74 num[i]=vis[i]; 75 if(i-w[i]>=0) c[i]+=num[i-w[i]]; 76 siz=en[i].size(); 77 for (int j=0;j<siz;++j) 78 num[ en[i][j] ]--; 79 } 80 memset(vis,0,sizeof(vis)); 81 memset(num,0,sizeof(num)); 82 for (int i=1;i<=m;++i) 83 if(s[i]>t[i]) en[ t[i] ].push_back(s[i]),vis[ s[i] ]++; 84 for (int i=n;i>=1;--i) 85 { 86 num[i]=vis[i]; 87 if(i+w[i]<=n) c[i]+=num[i+w[i]]; 88 siz=en[i].size(); 89 for (int j=0;j<siz;++j) 90 num[ en[i][j] ]--; 91 } 92 } 93 94 void bal() 95 { 96 for (int i=1;i<=m;++i) 97 L[i]=lca(s[i],t[i]); 98 for (int i=1;i<=m;++i) 99 { 100 int x=s[i],cnt=0; 101 while (x!=L[i]) 102 { 103 if(w[x]==cnt) c[x]++; 104 x=F[x][0]; 105 cnt++; 106 } 107 x=t[i],cnt=dep[ s[i] ]+dep[ t[i] ]-2*dep[ L[i] ]; 108 while (1) 109 { 110 if(w[x]==cnt) c[x]++; 111 if(x==L[i]) break; 112 x=F[x][0]; 113 cnt--; 114 } 115 } 116 } 117 118 void dfs1 (int x) 119 { 120 for (int i=firs[x];i;i=g[i].nex) 121 if(dep[ g[i].too ]>dep[x]) 122 { 123 dfs1(g[i].too); 124 num[x]+=num[ g[i].too ]; 125 } 126 } 127 128 void s1() 129 { 130 for (int i=1;i<=m;++i) 131 num[ t[i] ]++; 132 dfs1(1); 133 for (int i=1;i<=n;++i) 134 if(dep[i]-1==w[i]) c[i]=num[i]; 135 } 136 137 int main() 138 { 139 scanf("%d%d",&n,&m); 140 for (R i=1;i<n;++i) 141 { 142 scanf("%d%d",&x,&y); 143 add(x,y); 144 add(y,x); 145 } 146 for (R i=1;i<=n;++i) 147 scanf("%d",&w[i]); 148 for (R i=1;i<=m;++i) 149 scanf("%d%d",&s[i],&t[i]); 150 dep[1]=1; 151 dfs(1); 152 if(n%10==1||n%10==2) st(); 153 else if(n%10==4) link(); 154 else if(n%10==3) bal(); 155 else if(n%10==5) s1(); 156 for (int i=1;i<=n;++i) 157 printf("%d ",c[i]); 158 return 0; 159 }
只是终点为根的那一部分没有写,因为挺麻烦的,而且想到那个差不多就是正解了,但是不会实现于是就去看题解....
还是简述一下部分分的做法:
起点等于终点:只需要考虑对于每一个玩家的起点,观察员的$w$是否等于0即可; ---10pts √
$w_j=0$:和上一个一样 ---10pts √
NOIP送这么多分真的好吗...?
$n<=1000$:暴力; ---5pts √
树退化成一条链:我终于学会用vector均摊空间啦!分为从左往右和从右往左两种路径,先看从左往右的,在每个起点处打一个标记,再用vector存一下在每个点有哪些路径结束了,从左往右扫一遍。反着也是。---15pts √
s都是1:起点都是一样,终点接着像上一个那样均摊空间,从根节点开始往下dfs,统计到每个点为止有多少路径还没有结束,因为起点统一的原因,每个观察员是否能观察到也是固定的,如果他能观察到人,只要是到这里还没有结束的玩家都能被看到,并不是很复杂的样子。 ---20pts
t都是1:没写呀...但是现在想想也不是什么很难的东西,因为如果观察员能看到玩家,玩家一定是从观察员下面上来的,因为观察员的深度和$w$都是已知的,所以能看到的玩家的深度也是已知的,开一个桶统计目前深度为x的玩家有几个就好了,但是!即使是往下往上更新也会出问题,可能会更新到别的子树内的信息?只要这里能想到做法离满分就只差一点码力了,采用一种比较有趣的树上差分,在刚dfs到某个点时记录要用到的桶现在的值,等到dfs回来那个桶就会有新的值了,把这两个值相减得到的值就是它自己子树内的答案了!是不是很妙啊。 ---20pts √
满分:如果刚刚那个差分思路能想到,满分自然也不难了,两个部分分提示的还不够明显吗?拆路径。把每条路径拆成$s->lca$和$lca->t$两条,第一种如果能被看到肯定也是从下面爬上来的,因为起点终点均不唯一,所以不能一开始全加上,可以开两个vector,不过也可以用正数表示这里有一个开始了,负数表示有一个这个数的相反数深度的路径结束,我觉得这样更舒服一点。第二种也是这样。但是起点,终点深度都不固定了,看起来很难算,让我们来“理性分析”一下。
从观察员下面来的那些人,他们的深度就是$dep[x]+w[x]$,比较简单,对于从上面下来的人,他们走到观察员这里应该正好是第$w_i$个结点,也就是说:$dep[s]-dep[lca]+dep[x]-dep[lca]=w[x]$,这样移项一番就是一个定值,接着用差分桶维护。最后还有一个小细节,如果lca正好是一个满足条件的点,它就会被算两次,枚举每条路径的LCA把这种情况减掉。
这真是个神题啊...看懂了之后觉得无比自然,甚至感觉就是一种暴力的优化,可是不看题解就想不到这种奇妙的做法呢。来,上代码。
1 # include <cstdio> 2 # include <iostream> 3 # include <vector> 4 # include <cstring> 5 # define R register int 6 7 using namespace std; 8 9 const int maxn=3000000; 10 int n,m,x,y,h; 11 int w[maxn],c[maxn],s[maxn],t[maxn],vis[maxn],firs[maxn],dep[maxn],lca[maxn]; 12 int T[maxn<<1]; 13 int F[maxn][20]; 14 vector <int> v[maxn]; 15 int num[maxn]; 16 struct edge 17 { 18 int too,nex; 19 }g[maxn<<1]; 20 21 void add (int x,int y) 22 { 23 g[++h].too=y; 24 g[h].nex=firs[x]; 25 firs[x]=h; 26 } 27 28 void dfs (int x) 29 { 30 int j; 31 for (int i=firs[x];i;i=g[i].nex) 32 { 33 j=g[i].too; 34 if(dep[j]) continue; 35 dep[j]=dep[x]+1; 36 F[j][0]=x; 37 for (int i=1;i<=19;++i) 38 F[j][i]=F[ F[j][i-1] ][i-1]; 39 dfs(j); 40 } 41 } 42 43 int Lca (int x,int y) 44 { 45 if(dep[x]>dep[y]) swap(x,y); 46 for (int i=19;i>=0;--i) 47 if(dep[x]<=dep[y]-(1<<i)) 48 y=F[y][i]; 49 if(x==y) return x; 50 for (int i=19;i>=0;--i) 51 { 52 if(F[x][i]==F[y][i]) continue; 53 x=F[x][i]; 54 y=F[y][i]; 55 } 56 return F[x][0]; 57 } 58 59 void dfss (int x) 60 { 61 int j,p1=T[dep[x]+w[x]],siz; 62 for (R i=firs[x];i;i=g[i].nex) 63 { 64 j=g[i].too; 65 if(dep[j]<dep[x]) continue; 66 dfss(j); 67 } 68 siz=v[x].size(); 69 for (R i=0;i<siz;++i) 70 { 71 j=v[x][i]; 72 if(j>=0) T[j]++; 73 else T[-j]--; 74 } 75 c[x]+=T[dep[x]+w[x]]-p1; 76 } 77 78 void dfst (int x) 79 { 80 int j,p1=T[w[x]-dep[x]+n],siz; 81 for (R i=firs[x];i;i=g[i].nex) 82 { 83 j=g[i].too; 84 if(dep[j]<dep[x]) continue; 85 dfst(j); 86 } 87 siz=v[x].size(); 88 for (R i=0;i<siz;++i) 89 { 90 j=v[x][i]; 91 if(j>=0) T[j]++; 92 else T[-j]--; 93 } 94 c[x]+=T[w[x]-dep[x]+n]-p1; 95 } 96 97 int main() 98 { 99 scanf("%d%d",&n,&m); 100 for (R i=1;i<n;++i) 101 { 102 scanf("%d%d",&x,&y); 103 add(x,y); 104 add(y,x); 105 } 106 for (R i=1;i<=n;++i) 107 scanf("%d",&w[i]); 108 for (R i=1;i<=m;++i) 109 scanf("%d%d",&s[i],&t[i]); 110 dep[1]=1; 111 dfs(1); 112 for (R i=1;i<=m;++i) 113 lca[i]=Lca(s[i],t[i]); 114 115 for (R i=1;i<=m;++i) 116 v[ s[i] ].push_back(dep[ s[i] ]),v[ F[ lca[i] ][0] ].push_back( -dep[ s[i] ]); 117 dfss(1); 118 for (R i=1;i<=n*2;++i) v[i].clear(); 119 120 for (R i=1;i<=m;++i) 121 v[ t[i] ].push_back( -2*dep[ lca[i] ]+dep[ s[i] ]+n ),v[ F[ lca[i] ][0] ].push_back( -(-2*dep[ lca[i] ]+dep[ s[i] ]+n) ); 122 dfst(1); 123 124 for (R i=1;i<=m;++i) 125 if(dep[ s[i] ]-dep[ lca[i] ]==w[ lca[i] ]) c[ lca[i] ]--; 126 for (int i=1;i<=n;++i) 127 printf("%d ",c[i]); 128 return 0; 129 }
---shzr