树上差分

  一个写的很好的博客: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 }
部分分集锦(60pts)

  只是终点为根的那一部分没有写,因为挺麻烦的,而且想到那个差不多就是正解了,但是不会实现于是就去看题解....

  还是简述一下部分分的做法:

  起点等于终点:只需要考虑对于每一个玩家的起点,观察员的$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

posted @ 2018-08-16 19:14  shzr  阅读(221)  评论(0编辑  收藏  举报