1. 程式人生 > >P3241 [HNOI2015]開店 動態點分治

P3241 [HNOI2015]開店 動態點分治

更新 return 其他 truct 選擇 一段 統計 所有 swap

\(\color{#0066ff}{ 題目描述 }\)

風見幽香有一個好朋友叫八雲紫,她們經常一起看星星看月亮從詩詞歌賦談到人生哲學。最近她們靈機一動,打算在幻想鄉開一家小店來做生意賺點錢。

這樣的想法當然非常好啦,但是她們也發現她們面臨著一個問題,那就是店開在哪裏,面向什麽樣的人群。很神奇的是,幻想鄉的地圖是一個樹形結構,幻想鄉一共有 \(n\)個地方,編號為 \(1\)\(n\)\(n-1\) 條帶權的邊連接起來。每個地方都住著一個妖怪,其中第\(i\) 個地方的妖怪年齡是 \(x_i\)

妖怪都是些比較喜歡安靜的家夥,所以它們並不希望和很多妖怪相鄰。所以這個樹所有頂點的度數都小於或等於 \(3\)

。妖怪和人一樣,興趣點隨著年齡的變化自然就會變化,比如我們的\(18\) 歲少女幽香和八雲紫就比較喜歡可愛的東西。幽香通過研究發現,基本上妖怪的興趣只跟年齡有關,所以幽香打算選擇一個地方\(u\)\(u\) 為編號),然後在\(u\)開一家面向年齡在\(L\)\(R\) 之間(即年齡大於等於\(L\) 小於等於\(R\) )的妖怪的店。

也有可能\(u\) 這個地方離這些妖怪比較遠,於是幽香就想要知道所有年齡在\(L\)\(R\) 之間的妖怪,到點\(u\) 的距離的和是多少(妖怪到\(u\) 的距離是該妖怪所在地方到\(u\) 的路徑上的邊的權之和),幽香把這個稱為這個開店方案的方便值。

幽香她們還沒有決定要把店開在哪裏,八雲紫倒是準備了很多方案,於是幽香想要知道,對於每個方案,方便值是多少呢。

\(\color{#0066ff}{輸入格式}\)

第一行三個用空格分開的數\(n,Q\)\(A\) ,表示樹的大小、開店的方案個數和妖怪的年齡上限。

第二行\(n\) 個用空格分開的數\(x_1,x_2,\ldots,x_n;\)xi 表示第\(i\) 個地點妖怪的年齡,滿足\(0\le x_i\lt A\) 。(年齡是可以為\(0\)的,例如剛出生的妖怪的年齡為\(0\) 。)

接下來\(n-1\) 行,每行三個用空格分開的數\(a\)\(b\)\(c\) ,表示樹上的頂點\(a\)

\(b\) 之間有一條權為\(c(1\le c\le1000)\)的邊,\(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) 。

\(\color{#0066ff}{輸出格式}\)

對於每個方案,輸出一行表示方便值。

\(\color{#0066ff}{輸入樣例}\)

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

\(\color{#0066ff}{輸出樣例}\)

1603 
957 
7161 
9466 
3232 
5223 
1879 
1669 
1282 
0

\(\color{#0066ff}{數據範圍與提示}\)

滿足\(n\le1.5*10^5,Q\le2*10^5\) 。對於所有數據,滿足 \(A<=10^9\)

\(\color{#0066ff}{ 題解 }\)

動態點分治

建立點分樹

每個點維護4個vector,一個是自己子樹的age(有序加入),一個是對應的dis前綴和,我們考慮在age上二分找到L,R, 用這個下標在dis上收集ans

還有兩個數組類似,記錄對父親的貢獻

先在點分樹讓上把四個vector預處理出來,為了保證age有序,我們先把點按age從小到大排序,在更新

然後在點分樹上統計貢獻就行,註意卡二分邊界

#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
const int maxn = 2e5 + 10;
struct node {
    int to;
    LL dis;
    node *nxt;
    node(int to = 0, LL dis = 0, node *nxt = NULL): to(to), dis(dis), nxt(nxt) {}
    void *operator new (size_t) {
        static node *S = NULL, *T = NULL;
        return (S == T) && (T = (S = new node[1024]) + 1024), S++;
    }
};
LL n, Q, A;
using std::vector;
vector<LL> tofadis[maxn], tofaage[maxn], dis[maxn], age[maxn];
int siz[maxn], maxsiz[maxn], root, f[maxn][26], sum, rt, u[maxn], dep[maxn];
LL d[maxn][26], val[maxn];
node *head[maxn];
bool vis[maxn];
void add(int from, int to, LL dis) {
    head[from] = new node(to, dis, head[from]);
}
void init() {
    n = in(), Q = in(), A = in();
    for(int i = 1; i <= n; i++) val[i] = in();
    LL x, y, z;
    for(int i = 1; i < n; i++) {
        x = in(), y = in(), z = in();
        add(x, y, z), add(y, x, z);
    }
}
void getroot(int x, int fa) {
    siz[x] = 1;
    maxsiz[x] = 0;
    for(node *i = head[x]; i; i = i->nxt) {
        if(i->to == fa || vis[i->to]) continue;
        getroot(i->to, x);
        siz[x] += siz[i->to];
        maxsiz[x] = std::max(maxsiz[x], siz[i->to]);
    }
    maxsiz[x] = std::max(maxsiz[x], sum - siz[x]);
    if(maxsiz[x] < maxsiz[root]) root = x;
}
void build(int x) {
    vis[x] = true;
    for(node *i = head[x]; i; i = i->nxt) {
        if(vis[i->to]) continue;
        root = 0, sum = siz[i->to];
        getroot(i->to, 0);
        u[root] = x;
        build(root);
    }
}   
void build() {
    maxsiz[0] = sum = n;
    getroot(1, 0);
    rt = root;
    build(root);
}
void dfs(int x, int fa) {
    dep[x] = dep[fa] + 1;
    f[x][0] = fa;
    for(node *i = head[x]; i; i = i->nxt) {
        if(i->to == fa) continue;
        dfs(i->to, x);
        d[i->to][0] = i->dis;
    }
}
void beizeng() {
    dfs(1, 0);
    for(int j = 1; j <= 24; j++) 
        for(int i = 1; i <= n; i++) {
            f[i][j] = f[f[i][j - 1]][j - 1];
            d[i][j] = d[f[i][j - 1]][j - 1] + d[i][j - 1];
        }
}
LL LCA(int x, int y) {
    LL D = 0;
    if(dep[x] < dep[y]) std::swap(x, y);
    for(int i = 24; i >= 0; i--) if(dep[f[x][i]] >= dep[y]) D += d[x][i], x = f[x][i];
    if(x == y) return D;
    for(int i = 24; i >= 0; i--) if(f[x][i] != f[y][i]) D += d[x][i] + d[y][i], x = f[x][i], y = f[y][i];
    return D + d[x][0] + d[y][0];
}

bool cmp(const int &a, const int &b) { return val[a] < val[b]; }
void predoit() {
    static int id[maxn];
    for(int i = 1; i <= n; i++) id[i] = i;
    std::sort(id + 1, id + n + 1, cmp);
    for(int i = 1; i <= n; i++) {
        int now = id[i];
        //當前點的年齡,dis前綴和
        age[now].push_back(val[now]);
        dis[now].push_back(dis[now].empty()? 0 : dis[now].back());
        for(int o = now; u[o]; o = u[o]) {
            LL D = LCA(now, u[o]);
            //當前點父親的年齡,dis前綴和
            age[u[o]].push_back(val[now]);
            dis[u[o]].push_back(dis[u[o]].empty()? D : dis[u[o]].back() + D);
            //當前點對父親的貢獻,同樣維護
            tofaage[o].push_back(val[now]);
            tofadis[o].push_back(tofadis[o].empty()? D : tofadis[o].back() + D);
        }
    }
}

LL calc(int pos, LL L, LL R) {
    LL posl, posr;
    LL ans;
    //初始為pos子樹自己的貢獻
    posl = std::lower_bound(age[pos].begin(), age[pos].end(), L) - age[pos].begin() - 1;
    posr = std::upper_bound(age[pos].begin(), age[pos].end(), R) - age[pos].begin() - 1;
    //註意卡邊界
    if(R < age[pos].front() || L > age[pos].back()) ans = 0;
    else if(posl == -1) ans = dis[pos][posr];
    else ans = dis[pos][posr] - dis[pos][posl];
    for(int o = pos; u[o]; o = u[o]) {
        LL tot, fal, far;
        //統計ans在age裏二分,在dis裏收集答案
        posl = std::lower_bound(tofaage[o].begin(), tofaage[o].end(), L) - tofaage[o].begin() - 1;
        posr = std::upper_bound(tofaage[o].begin(), tofaage[o].end(), R) - tofaage[o].begin() - 1;
        
        if(R < tofaage[o].front() || L > tofaage[o].back()) tot = 0;
        else if(posl == -1) tot = tofadis[o][posr];
        else tot = tofadis[o][posr] - tofadis[o][posl];
        //這個減去的是自己對父親的貢獻,現在要統計父親的其他子樹的貢獻,自己的貢獻會重(父親子樹的貢獻-自己子樹的貢獻=兄弟子樹的貢獻(對父親的貢獻))
        ans -= tot;

        LL D = LCA(u[o], pos);
        fal = std::lower_bound(age[u[o]].begin(), age[u[o]].end(), L) - age[u[o]].begin() - 1;
        far = std::upper_bound(age[u[o]].begin(), age[u[o]].end(), R) - age[u[o]].begin() - 1;

        if(R < age[u[o]].front() || L > age[u[o]].back()) tot = 0;
        else if(fal == -1) tot = dis[u[o]][far];
        else tot = dis[u[o]][far] - dis[u[o]][fal];
        //父親子樹到父親的貢獻,因為上面已經減去了那部分,所以不會重復
        ans += tot;
        //上面的是父親其他子樹到父親的貢獻,這是其中一段距離,我們的目的是他們到pos的距離,所以還差父親到pos的距離
        ans += ((far - fal) - (posr - posl)) * D;
    }
    return ans;
}           
void query() {
    LL pos, a, b, L, R, ans = 0;
    while(Q --> 0) {
        pos = in(), a = in(), b = in();
        L = std::min((a + ans) % A, (b + ans) % A), R = std::max((a + ans) % A, (b + ans) % A);
        ans = calc(pos, L, R);
        printf("%lld\n", ans);
    }
}
int main() { 
    init();
    build();
    beizeng();
    predoit();
    query();
    return 0;
}

P3241 [HNOI2015]開店 動態點分治