【NOIP2016提高】天天爱跑步的解析(树上差分+LCA+桶)

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/83065090

题目:luogu1600.

题目大意:给定一棵树和树上每个节点的w_i,现在给出m对s_it_i,表示从s_it_i会有一个人沿树上的路径走过,并且这个人每秒移动到下一个点.现在每个人都在时刻0走出,询问每一个点i,请你输出第w_i时刻有多少人会在点i.

这道题真的是史上最难的NOIP题啊...

我们现在一步步分析部分分,一步步逼近正解.

部分分1:s=1.

若s=1,很容易发现一个人i会对点j做出贡献,仅当t_i有一个祖先为j且deep[j]+w_j=deep[t_i].

我们可以记录一个计数数组cnt,也就是一个桶,当我们退出一个点k时,我们将cnt[deep[k]]加以k为终点的路径数量,并计算出答案ans[k]=cnt[deep[k]+w_k].

部分分2:t=1.

若t=1,很容易发现一个人i会对点j做出贡献,仅当s_i有一个祖先为j且deep[s_i]+w_j=deep[j].

我们将这个式子变式即可得到deep[j]-w_j=deep[s_i].

s=1类似的,记录一个计数数组cnt,当我们进入一个点k时,我们将cnt[deep[k]]加以k为起点的路径数量,并计算答案ans[k]=cnt[deep[k]-w_k].

部分分3:链.

如果是链的话,那么我们可以发现一个人要么是一直往右走,要么是一直往左走.

那么我们将人分为两种,一种是往左走的,一种是往右走的.

然后我们依旧记录一个桶,往左走的我们从1开始递归,往右走的我们从n开始递归.

当我们往左走时,我们退出一个点时,我们将这个点的答案计算出来,然后将所有已它为终点的起点的贡献去掉,这样就能保证不对后面的答案产生影响,具体这个功能可以用vector实现.

AC做法.

AC做法其实就是链的数据做法的拓展.
我们只需要将一条路径分成两条链,一条从终点到lca,一条从起点到lca.

拆完链之后发现这是一个树上差分,不过要注意若LCA计入了两次,要去掉一次.

那么很容易就可以做了.

还有这道题其实是可以写dsu on tree和线段树合并的,思路就是因为有子树查询cnt数组,有兴趣的自己去写写吧,我放弃了.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=300000;
int n,m;
int s[N+9],t[N+9],w[N+9];
int lca[N+9],dis[N+9];
int cnt[3*N+9],ans[N+9],num[N+9];
vector<int>q1[N+9],q2[N+9],q3[N+9];
struct side{
  int y,next;
}e[N*2+9];
int lin[N+9],top;
int deep[N+9],gr[N+9][20];
bool use[N+9];
void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}
void dfs_lca(int k,int fa){
  gr[k][0]=fa;
  deep[k]=deep[fa]+1;
  for (int i=1;i<19;i++)
    gr[k][i]=gr[gr[k][i-1]][i-1];
  for (int i=lin[k];i;i=e[i].next)
    if (fa^e[i].y) dfs_lca(e[i].y,k);
}
int LCA(int x,int y){
  if (deep[x]<deep[y]) swap(x,y);
  int dep=deep[x]-deep[y];
  for (int i=0;i<19;i++)
    if (dep>>i&1) x=gr[x][i];
  for (int i=18;i>=0;i--)
    if (gr[x][i]^gr[y][i]) x=gr[x][i],y=gr[y][i];
  return x^y?gr[x][0]:x;
}
#define vt vector<int>::iterator
void dfs_up(int k){
  int now=cnt[deep[k]+w[k]]; 
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^gr[k][0]) dfs_up(e[i].y);
  cnt[deep[k]]+=num[k];
  ans[k]+=cnt[deep[k]+w[k]]-now;
  for (vt it=q1[k].begin();it!=q1[k].end();it++)
    cnt[deep[*it]]--;
}
void dfs_down(int k){
  int now=cnt[w[k]-deep[k]+N];
  for (int i=lin[k];i;i=e[i].next)
    if (gr[k][0]^e[i].y) dfs_down(e[i].y);
  for (vt it=q2[k].begin();it!=q2[k].end();it++)
    cnt[*it+N]++;
  ans[k]+=cnt[w[k]-deep[k]+N]-now;
  for (vt it=q3[k].begin();it!=q3[k].end();it++)
    cnt[*it+N]--;
}
#undef vt
Abigail into(){
  scanf("%d%d",&n,&m);
  int x,y;
  for (int i=1;i<n;i++){
    scanf("%d%d",&x,&y);
    ins(x,y);ins(y,x);
  }
  for (int i=1;i<=n;i++)
    scanf("%d",&w[i]);
  for (int i=1;i<=m;i++)
    scanf("%d%d",&s[i],&t[i]);
}
Abigail work(){
  dfs_lca(1,0);
  for (int i=1;i<=m;i++){
    lca[i]=LCA(s[i],t[i]);
    dis[i]=deep[s[i]]+deep[t[i]]-2*deep[lca[i]];
    num[s[i]]++;
    q1[lca[i]].push_back(s[i]);
    q2[t[i]].push_back(dis[i]-deep[t[i]]);
    q3[lca[i]].push_back(dis[i]-deep[t[i]]); 
  }
  dfs_up(1);
  dfs_down(1);
  for (int i=1;i<=m;i++)
    if (deep[s[i]]==deep[lca[i]]+w[lca[i]]) ans[lca[i]]--;      //去重 
}
Abigail outo(){
  for (int i=1;i<=n;i++)
    printf("%d ",ans[i]);
  puts("");
}
int main(){
  into();
  work();
  outo();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/83065090