1. 程式人生 > >【NOIP2016提高】天天愛跑步的解析(樹上差分+LCA+桶)

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

題目: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;
}