【bzoj4012】[HNOI2015]開店 動態樹分治+二分查找
題目描述
風見幽香有一個好朋友叫八雲紫,她們經常一起看星星看月亮從詩詞歌賦談到人生哲學。最近她們靈機一動,打算在幻想鄉開一家小店來做生意賺點錢。這樣的想法當然非常好啦,但是她們也發現她們面臨著一個問題,那就是店開在哪裏,面向什麽樣的人群。很神奇的是,幻想鄉的地圖是一個樹形結構,幻想鄉一共有 n個地方,編號為 1 到 n,被 n-1 條帶權的邊連接起來。每個地方都住著一個妖怪,其中第 i 個地方的妖怪年齡是 x_i。妖怪都是些比較喜歡安靜的家夥,所以它們並不希望和很多妖怪相鄰。所以這個樹所有頂點的度數都小於或等於 3。妖怪和人一樣,興趣點隨著年齡的變化自然就會變化,比如我們的 18 歲少女幽香和八雲紫就比較喜歡可愛的東西。幽香通過研究發現,基本上妖怪的興趣只跟年齡有關,所以幽香打算選擇一個地方 u(u為編號),然後在 u開一家面向年齡在 L到R 之間(即年齡大於等於 L、小於等於 R)的妖怪的店。也有可能 u這個地方離這些妖怪比較遠,於是幽香就想要知道所有年齡在 L 到 R 之間的妖怪,到點 u 的距離的和是多少(妖怪到 u 的距離是該妖怪所在地方到 u 的路徑上的邊的權之和) ,幽香把這個稱為這個開店方案的方便值。幽香她們還沒有決定要把店開在哪裏,八雲紫倒是準備了很多方案,於是幽香想要知道,對於每個方案,方便值是多少呢。
輸入
第一行三個用空格分開的數 n、Q和A,表示樹的大小、開店的方案個數和妖怪的年齡上限。
第二行n個用空格分開的數 x_1、x_2、…、x_n,x_i 表示第i 個地點妖怪的年齡,滿足0<=x_i<A。(年齡是可以為 0的,例如剛出生的妖怪的年齡為 0。)接下來 n-1 行,每行三個用空格分開的數 a、b、c,表示樹上的頂點 a 和 b 之間有一條權為c(1 <= c <= 1000)的邊,a和b 是頂點編號。
接下來Q行,每行三個用空格分開的數 u、 a、 b。對於這 Q行的每一行,用 a、b、A計算出 L和R,表示詢問“在地方 u開店,面向妖怪的年齡區間為[L,R]的方案的方便值是多少”。對於其中第 1 行,L 和 R 的計算方法為:L=min(a%A,b%A), R=max(a%A,b%A)。對於第 2到第 Q行,假設前一行得到的方便值為 ans,那麽當前行的 L 和 R 計算方法為: L=min((a+ans)%A,(b+ans)%A), R=max((a+ans)%A,(b+ans)%A)。
輸出
對於每個方案,輸出一行表示方便值。
樣例輸入
10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4
樣例輸出
1603
957
7161
9466
3232
5223
1879
1669
1282
0
題解
動態樹分治+二分查找
查找距離和的方法參考 bzoj3924 。即維護子樹中所有點到該點的距離和以及所有點到該點父親節點的距離和。
但是本題帶了“年齡”的限制,因此不能直接維護數組。
考慮到本題沒有修改操作,因此可以直接把維護的距離和數組改為vector,記錄上面的信息,同時記錄“年齡”。然後把每個vector按照“年齡”從小到大排序,並求出前綴和,然後上二分查找即可。註意此時需要加上兩個虛擬的“哨兵節點”以防止越界。
時間復雜度$O(n\log^2n)$
#include <cstdio> #include <vector> #include <algorithm> #define N 150010 using namespace std; typedef long long ll; struct data { int val; ll dis , sum; data() {} data(int a , ll b , ll c) {val = a , dis = b , sum = c;} bool operator<(const data &a)const {return val == a.val ? dis == a.dis ? sum < a.sum : dis < a.dis : val < a.val;} }; vector<data> va[N] , vb[N]; int a[N] , head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , pos[N] , log[N << 1] , tot; int si[N] , mx[N] , sum , root , vis[N] , fa[N]; ll deep[N] , md[20][N << 1]; void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x , int fa) { int i; pos[x] = ++tot , md[0][tot] = deep[x]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa) deep[to[i]] = deep[x] + len[i] , dfs(to[i] , x) , md[0][++tot] = deep[x]; } ll calc(int x , int y) { ll t = deep[x] + deep[y]; x = pos[x] , y = pos[y]; if(x > y) swap(x , y); int k = log[y - x + 1]; return t - 2 * min(md[k][x] , md[k][y - (1 << k) + 1]); } void getroot(int x , int fa) { int i; si[x] = 1 , mx[x] = 0; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) getroot(to[i] , x) , si[x] += si[to[i]] , mx[x] = max(mx[x] , si[to[i]]); mx[x] = max(mx[x] , sum - si[x]); if(mx[x] < mx[root]) root = x; } void solve(int x) { int i; vis[x] = 1; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]]) sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , fa[root] = x , solve(root); } ll query(int x , int p) { int i , t; ll ans = 0; for(i = x ; i ; i = fa[i]) { t = lower_bound(va[i].begin() , va[i].end() , data(p , 0 , 0)) - va[i].begin() - 1; ans += va[i][t].sum + t * calc(i , x); } for(i = x ; fa[i] ; i = fa[i]) { t = lower_bound(vb[i].begin() , vb[i].end() , data(p , 0 , 0)) - vb[i].begin() - 1; ans -= vb[i][t].sum + t * calc(fa[i] , x); } return ans; } int main() { int n , m , k , i , j , x , y , z; ll last = 0; scanf("%d%d%d" , &n , &m , &k); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z); dfs(1 , 0); for(i = 2 ; i <= tot ; i ++ ) log[i] = log[i >> 1] + 1; for(i = 1 ; (1 << i) <= tot ; i ++ ) for(j = 1 ; j <= tot - (1 << i) + 1 ; j ++ ) md[i][j] = min(md[i - 1][j] , md[i - 1][j + (1 << (i - 1))]); mx[0] = 1 << 30 , sum = n , getroot(1 , 0) , solve(root); for(i = 1 ; i <= n ; i ++ ) for(j = i ; j ; j = fa[j]) va[j].push_back(data(a[i] , calc(i , j) , 0)) , vb[j].push_back(data(a[i] , calc(i , fa[j]) , 0)); for(i = 1 ; i <= n ; i ++ ) { va[i].push_back(data(-1 , 0 , 0)) , va[i].push_back(data(1 << 30 , 0 , 0)); vb[i].push_back(data(-1 , 0 , 0)) , vb[i].push_back(data(1 << 30 , 0 , 0)); sort(va[i].begin() , va[i].end()) , sort(vb[i].begin() , vb[i].end()); for(j = 1 ; j < (int)va[i].size() ; j ++ ) va[i][j].sum = va[i][j - 1].sum + va[i][j].dis; for(j = 1 ; j < (int)vb[i].size() ; j ++ ) vb[i][j].sum = vb[i][j - 1].sum + vb[i][j].dis; } while(m -- ) { scanf("%d%d%d" , &x , &y , &z) , y = (y + last) % k , z = (z + last) % k; if(y > z) swap(y , z); printf("%lld\n" , last = query(x , z + 1) - query(x , y)); } return 0; }
【bzoj4012】[HNOI2015]開店 動態樹分治+二分查找