1. 程式人生 > >[NOIP2016] 天天愛跑步

[NOIP2016] 天天愛跑步

多少 避免 ont find ans 貢獻 noi int bool

~~~題面~~~

題解:

很久以前就想寫了,一直沒敢做,,,不過今天寫完沒怎麽調就過了還是很開心的。

首先我們觀察到跑步的人數是很多的,要一條一條的遍歷顯然是無法承受的,因此我們要考慮更加優美的方法。

首先我們假設觀察者沒有時間的限制,一天到晚都在觀察。那麽我們可以想到一個很顯然的做法——差分。這樣就可以很方便的求出一個點被經過了多少次。

貌似這樣再加想一下就可以直接用樹鏈剖分+差分搞了。

但是還有更好的算法,是O(n)的。

而且還是比較好寫的。

首先我們觀察可以被統計到的點要符合一個什麽條件。

技術分享圖片

設點j上的觀察者在w[j]的時候觀察,那麽也就是說從s出發,要剛好經過w[j]到達點j,才可以被點j上的觀察者統計到。

由此我們可以列出兩個式子:

1,當s在j的子樹內,而t在j的上方時。

  $w[j] + dep[j] = dep[s_i]$

2,當t在j的子樹內,而s在j的上方時

  $dis[i] - (dep[t_i] - dep[j]) = w[j]$ 其中dis[i]表示s ---> t的路程長度

  因為dis[i] - (dep[t_i] - dep[j])其實就是s ---> j的路徑長,所以這個式子就顯然成立了。

  如果我們將帶i的放在左邊,帶j的放在右邊,那麽將會有

  $dis[i] - dep[t_i] = w[j] - dep[j]$

那s和t都在j的子樹內怎麽辦?

(都在上方顯然不會統計到)

  現在這時如果我們還是套用上面的式子,那麽會有兩種情況

  (1),j是(s,t)的LCA,那麽此時兩個式子一旦其中之一被滿足,另外一個就一定會被滿足(因為兩個式子的實質是一樣的),那麽s和t將分別對j產生一次貢獻(否則沒有貢獻)

  (2),j不是(s,t)的LCA,那麽此時兩個式子可能會被滿足,s和t不一定會對j產生貢獻。但是這種情況下,不論式子是否被滿足,s和t都是不應該對j產生貢獻的。
  那麽我們怎麽避免這種情況?

    首先我們註意到一旦這種情況出現,LCA[s,t]將會是s和t最後一次可能產生貢獻的地方,因此我們要在LCA[s,t]處,消除s和t的影響,使得s和t無法對上面的點產生貢獻。

那麽我們應該如何統計呢?

  註意到所有的式子都可以被表示為左邊只有關於i的,右邊只有關於j的情況,因此我們完全可以將式子的左邊和右邊單獨計算。即分別用兩個桶統計關於兩個式子的滿足情況。

比如說我們定義int bu[600100]; 然後每次遇到一個$s_i$的時候,我們就令$dep[s_i]++$。然後我們在每進入一個點時,都記錄一個tmp = bu[w[j] + dep[j]],那麽桶內對應位置元素的個數就代表滿足

$dep[s_i] = t$(t為桶中位置)的元素個數。因此我們訪問bu[w[j] + dep[j]]就可以獲取滿足 $w[j] + dep[j] = dep[s_i]$ 的元素個數,而之所以要在進入之前先記錄一下位置,則是為了準確獲取子樹內的貢獻,保證不被之前的東西幹擾。

  對第二個式子的處理方式也是類似的,只不過因為第二個式子中出現了減號,而且減號旁邊大小關系不確定,因此我們需要的桶中位置可能為負,所以我們統一加上一個較大的數,將本來要占據負數的數組整體向後移位就可以了。

具體實現看代碼。

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 501000
  5 #define ac 910000//要開這麽大。。。
  6 #define getchar() *o++
  7 char READ[12000100], *o = READ;
  8 int n, m;
  9 int w[AC], ans[AC], s[AC], t[AC], dis[AC], dep[AC], may[ac], id[ac];
 10 int Head[AC], Next[ac], date[ac], tot;
 11 struct edge{
 12     int Head[AC], Next[ac], date[ac], tot;
 13     inline void add(int f, int w)
 14     {
 15         date[++tot] = w, Next[tot] = Head[f], Head[f] = tot;
 16         date[++tot] = f, Next[tot] = Head[w], Head[w] = tot;
 17     }
 18 }E1,E2,E3;//詢問的邊(LCA),查詢的邊(ans) 
 19 
 20 inline int read()
 21 {
 22     int x = 0; char c = getchar();
 23     while(c > 9 || c < 0) c = getchar();
 24     while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar();
 25     return x;
 26 }
 27 
 28 inline void add(int f, int w)
 29 {
 30     date[++tot] = w, Next[tot] = Head[f], Head[f] = tot;
 31     date[++tot] = f, Next[tot] = Head[w], Head[w] = tot;
 32 }
 33 
 34 inline void add1(int f, int w, int S)
 35 {
 36     E3.date[++tot] = w, E3.Next[tot] = E3.Head[f], E3.Head[f] = tot, may[tot] = S;
 37 }
 38 
 39 inline void add2(int f, int w)//這裏只能連單向邊
 40 {
 41     E2.date[++tot] = w, E2.Next[tot] = E2.Head[f], E2.Head[f] = tot;
 42 }
 43 
 44 void pre()
 45 {
 46     int a, b;
 47     n = read(), m = read();
 48     for(R i = 1; i < n; i++)
 49     {
 50         a = read(), b = read();
 51         add(a, b);
 52     }
 53     for(R i = 1; i <= n; i++) w[i] = read();
 54     E1.tot = E2.tot = tot = 1;
 55     for(R i = 1; i <= m; i++) 
 56     {
 57         s[i] = read(), t[i] = read();
 58         E1.add(s[i], t[i]);
 59         add1(s[i], i, 0);//標記為開始節點
 60         add1(t[i], i, 1);//標記為結束節點
 61         id[E1.tot - 1] = id[E1.tot] = i;
 62     }
 63 }
 64 
 65 struct get_LCA{ 
 66     int father[AC], LCA[ac]; bool z[AC];
 67     
 68     inline int find(int x)
 69     {
 70          return (x == father[x]) ? x : father[x] = find(father[x]);
 71     }
 72     
 73     void dfs(int x)
 74     {
 75         int now;
 76         z[x] = true;
 77         for(R i = Head[x]; i; i = Next[i])
 78         {
 79             now = date[i];
 80             if(z[now]) continue; 
 81             dep[now] = dep[x] + 1;
 82             dfs(now);
 83             father[now] = x;//訪問完就要改父親了
 84         }
 85         for(R i = E1.Head[x]; i; i = E1.Next[i])
 86         {
 87             now = E1.date[i];
 88             if(z[now] && !LCA[i ^ 1]) 
 89             {
 90             //    printf("%d %d\n", x, now);
 91                 LCA[i] = find(now);
 92                 dis[id[i]] = dep[x] - dep[LCA[i]] + dep[now] - dep[LCA[i]];
 93                 add2(LCA[i], id[i]);//將LCA和詢問聯系起來
 94             }
 95         }
 96     }
 97     
 98     void getLCA()
 99     {
100         for(R i = 1; i <= n; i++) father[i] = i;
101         dep[1] = 1;
102         dfs(1);
103     }
104 }LCA;
105 
106 #define k 600000
107 struct difference{
108     int bu[ac], b[ac * 3]; 
109     void dfs(int x, int fa)
110     {
111         int tmp = bu[dep[x] + w[x]], rnt = b[w[x] - dep[x] + k], now;
112         for(R i = E3.Head[x]; i; i = E3.Next[i])//加上當前節點的貢獻
113         {
114             now = E3.date[i];
115             if(!may[i]) ++bu[dep[x]];//如果是開始節點
116             else ++b[dis[now] - dep[x] + k];//等式兩邊同時+k
117         }
118         for(R i = Head[x]; i; i = Next[i])//遍歷子樹
119         {
120             now = date[i];
121             if(now == fa) continue;
122             dfs(now, x);
123         }
124         ans[x] = bu[dep[x] + w[x]] - tmp + b[w[x] - dep[x] + k] - rnt;
125         for(R i = E2.Head[x]; i; i = E2.Next[i])//查看當前節點是哪些點對的LCA
126         {
127             now = E2.date[i];
128             if(dep[s[now]] == dep[x] + w[x]) --ans[x];//如果造成了貢獻,那麽必定是雙倍,因此要減去
129             --bu[dep[s[now]]];//減去貢獻
130             --b[dis[now] - dep[t[now]] + k];//等式兩邊同時+k!
131         }
132     }
133     
134 }get;
135 
136 void work()
137 {
138     for(R i = 1; i <= n; i++) printf("%d ", ans[i]);
139     printf("\n");
140 }
141 
142 int main()
143 {
144     freopen("in.in", "r", stdin);
145     fread(READ, 1, 12000000, stdin);
146     pre();
147     LCA.getLCA();
148     get.dfs(1, 0);
149     work();
150     fclose(stdin);
151     return 0;
152 }

[NOIP2016] 天天愛跑步