1. 程式人生 > >[洛谷P1600] 天天愛跑步

[洛谷P1600] 天天愛跑步

數據規模 truct 們的 路徑和 整合 bool last err 遊戲

洛谷題目鏈接:天天愛跑步

題目描述

小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。《天天愛跑步》是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。

這個遊戲的地圖可以看作一一棵包含 \(n\)個結點和 \(n-1\)條邊的樹, 每條邊連接兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從\(1\)\(n\)的連續正整數。

現在有\(m\)個玩家,第\(i\)個玩家的起點為 \(S_i\)?,終點為 \(T_i\)? 。每天打卡任務開始時,所有玩家在第\(0\)秒同時從自己的起點出發, 以每秒跑一條邊的速度, 不間斷地沿著最短路徑向著自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以每個人的路徑是唯一的)

小c想知道遊戲的活躍度, 所以在每個結點上都放置了一個觀察員。 在結點\(j\)的觀察員會選擇在第\(W_j\)?秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第\(W_j\)秒也理到達了結點 \(j\) 。 小C想知道每個觀察員會觀察到多少人?

註意: 我們認為一個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一 段時間後再被觀察員觀察到。 即對於把結點\(j\)作為終點的玩家: 若他在第\(W_j\)?秒前到達終點,則在結點\(j\)的觀察員不能觀察到該玩家;若他正好在第\(W_j\)秒到達終點,則在結點\(j\)的觀察員可以觀察到這個玩家。

輸入輸出格式

輸入格式:

第一行有兩個整數\(n\)

\(m\) 。其中\(n\)代表樹的結點數量, 同時也是觀察員的數量, \(m\)代表玩家的數量。

接下來 \(n- 1\)行每行兩個整數\(u\)\(v\),表示結點 \(u\)到結點 \(v\)有一條邊。

接下來一行 \(n\)個整數,其中第\(j\)個整數為\(W_j\)? , 表示結點jjj出現觀察員的時間。

接下來 \(m\)行,每行兩個整數\(S_i\)?,和\(T_i\)?,表示一個玩家的起點和終點。

對於所有的數據,保證\(1\leq S_i,T_i\leq n, 0\leq W_j\leq n\)

輸出格式:

輸出1行 \(n\)個整數,第\(j\)個整數表示結點\(j\)

的觀察員可以觀察到多少人。

輸入輸出樣例

輸入樣例#1:

6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

輸出樣例#1:

2 0 0 1 1 1

輸入樣例#2:

5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5

輸出樣例#2:

1 2 1 0 1

說明

【樣例1說明】

對於\(1\)號點,\(W_i=0\),故只有起點為1號點的玩家才會被觀察到,所以玩家\(1\)和玩家\(2\)被觀察到,共有\(2\)人被觀察到。

對於\(2\)號點,沒有玩家在第\(2\)秒時在此結點,共\(0\)人被觀察到。

對於\(3\)號點,沒有玩家在第\(5\)秒時在此結點,共\(0\)人被觀察到。

對於\(4\)號點,玩家\(1\)被觀察到,共\(1\)人被觀察到。

對於\(5\)號點,玩家\(1\)被觀察到,共\(1\)人被觀察到。

對於\(6\)號點,玩家\(3\)被觀察到,共\(1\)人被觀察到。

【子任務】

每個測試點的數據規模及特點如下表所示。 提示: 數據範圍的個位上的數字可以幫助判斷是哪一種數據類型。

技術分享圖片


題意: 一顆\(n\)個點的樹上有\(m\)條路徑,每條路徑的起點有一個玩家,並且會在\(0\)時刻開始向終點走.每個點有一個觀察員,他能觀察到這個玩家當且僅當這個玩家在觀察員出現的時候經過這個點.問每個觀察員能觀察到的玩家數量.

題解: 這道題我們先針對每一個部分分數據點來考慮.

pts10:所有人的起點等於終點

直接統計所有有玩家的位置,每個位置觀察員出現時間為\(0\)就輸出答案,否則輸出\(0\).

pts10:\(W_j=0\)

直接統計所有玩家初始位置.

pts5:\(n=993,m=993\)

模擬玩家在路徑上走的過程,對於一條路徑,計算路徑上每個觀察員能否觀察到這個玩家.

namespace pts25 {
    int ans[N], dep[N], fa[N];

    void dfs(int x, int f, int deep) {
        dep[x] = deep, fa[x] = f;
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != fa[x]) dfs(e[i].to, x, deep+1);
    }

    int lca(int x, int y) {
        if(dep[x] < dep[y]) swap(x, y);
        while(dep[x] > dep[y]) x = fa[x];
        while(x != y) x = fa[x], y = fa[y];
        return x;
    }

    void check(int x, int y) {
        int LCA = lca(x, y), size = dep[x]+dep[y]-dep[LCA]*2+1, cnt = 0, cnt1 = 0;
        for(; x != LCA; x = fa[x], cnt++, cnt1++)
            if(come[x] == cnt) ans[x]++;
        if(come[LCA] == cnt++) ans[LCA]++;
        size = size-cnt+cnt1;
        for(; y != LCA; y = fa[y], size--)
            if(come[y] == size) ans[y]++;
    }

    void work() {
        dfs(1, -1, 1);
        for(int i = 1; i <= m; i++) check(a[i].x, a[i].y);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
        printf("\n");
        exit(0);
    }
}

pts15:樹退化成一條鏈

將向左走和向右走分開統計(下面默認向右走).
考慮對於一個觀察員\(i\)觀察到某個玩家\(j\)的條件,發現只有當玩家的出發點\(st_j=i-come[i]\)的時候才能被觀察到,其中\(come[i]\)為第\(i\)個觀察員觀察的時間.可以自己畫圖理解一下.

namespace pts_chain {
    int b[N], ans[N], cnt1 = 0, cnt2 = 0;

    struct node {
        int pos, upd, val;
    } upd1[N*2], upd2[N*2];

    bool cmp1(node a, node b) { return a.pos < b.pos; }
    bool cmp2(node a, node b) { return a.pos > b.pos; }

    void work() {
        for(int i = 1; i <= m; i++)
            if(a[i].x == a[i].y && come[a[i].x] == 0) ans[a[i].x]++;
        for(int i = 1; i <= m; i++) {
            if(a[i].x < a[i].y) {
                upd1[++cnt1] = (node) { a[i].x, a[i].x, 1 };
                upd1[++cnt1] = (node) { a[i].y+1, a[i].x, -1 };
            }
            if(a[i].x > a[i].y) {
                upd2[++cnt2] = (node) { a[i].x, a[i].x, 1 };
                upd2[++cnt2] = (node) { a[i].y-1, a[i].x, -1 };
            }
        }
        sort(upd1+1, upd1+cnt1+1, cmp1), sort(upd2+1, upd2+cnt2+1, cmp2);

        for(int i = 1, pos = 1; i <= n; i++) {
            for(; pos <= cnt1 && upd1[pos].pos <= i; pos++)
                b[upd1[pos].upd] += upd1[pos].val;
            ans[i] += b[i-come[i]];
        }
        for(int i = n, pos = 1; i >= 1; i--) {
            for(; pos <= cnt2 && upd2[pos].pos >= i; pos++) {
                b[upd2[pos].upd] += upd2[pos].val;
            }
            ans[i] += b[i+come[i]];
        }
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
        printf("\n");
        exit(0);
    }
}

所有的\(S_i=1\)

因為所有起點都是\(1\),所以所有玩家可以看作是同時從\(1\)節點出發,這樣他們的時間軸就是統一的了.一個觀察員要觀察到一個玩家,那麽這個觀察員所在的深度\(dep[i]=come[i]\),其中\(1\)節點的深度為\(0\).如果不滿足這個條件,一定是沒有玩家可以被他觀察到的,因為所有玩家的起點都是\(1\),並都會向下走,所以到達某個深度的時間就是這個深度.

那麽這樣我們就可以用樹上差分來統計一下樹上的路徑被經過了多少次,然後一次\(dfs\)統計差分,最後如果深度等於觀察時間的觀察員就輸出統計的次數,否則輸出\(0\).

namespace pts_si1 {
    int ans[N], dep[N];

    void dfs(int x, int f, int deep) {
        dep[x] = deep;
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs(e[i].to, x, deep+1), ans[x] += ans[e[i].to];
    }

    void work(path a[], int m) {
        for(int i = 1; i <= m; i++) ans[a[i].y]++;
        dfs(1, -1, 0);
        for(int i = 1; i <= n; i++) {
            if(come[i] == dep[i]) printf("%d ", ans[i]);
            else printf("0 ");
        }
        exit(0);
    }
}

所有的\(T_i=1\)

其實這個部分分的做法像是暴力模擬的做法和所有\(S_i=1\)的做法的組合.

同樣的我們考慮統計一個觀察員\(i\)如何才能觀察到玩家\(j\).要使\(i\)能觀察到\(j\),則有\(dep[j]-dep[i]=come[i]\),將\(i,j\)分開放,則有\(dep[i]+come[i]=dep[j]\),也就是說,我們在統計差分數組的時候,對於某一個點\(i\),它能觀察到的所有節點是它子樹中深度為\(dep[i]+come[i]\)的個數.這個統計我們同樣可以用樹上差分實現.

但是在統計的過程中,因為我們需要將一個節點子節點的桶的狀態傳向父節點,而這樣就會有可能在統計的時候統計到別的子樹中去,所以我們需要先減去之前狀態所造成的貢獻,然後在統計完這個節點後再給答案加上這顆子樹的貢獻.

namespace pts_ti1 {
    int ans[N], dep[N], b[N], upd[N];

    void dfs(int x, int f, int deep) {
        dep[x] = deep, ans[x] -= b[come[x]+dep[x]];
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs(e[i].to, x, deep+1);
        b[dep[x]] += upd[x];
        ans[x] += b[come[x]+dep[x]];
    }

    void work(path a[], int m) {
        for(int i = 1; i <= m; i++) upd[a[i].x]++;
        dfs(1, -1, 0);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
        printf("\n");
        exit(0);
    }
}

下面來講一下滿分做法.
其實如果所有部分分都會寫了,正解就很簡單了

其實對於一個玩家的路徑,我們可以拆成一條向上的路徑和一條向下的路徑,只不過它的起點/終點不是\(1\).這時候,我們就需要給樹上的貢獻差分一下.也就是對於一條路徑(默認向上走)\((x->y)\),我們需要在\(x\)點加入這條路徑的貢獻,然後需要在\(fa[y]\)的地方減去這個貢獻,防止貢獻被多次統計.

而這樣統計的話,我們就會有一個問題:在\(fa[y]\)修改的貢獻仍然是\(x\)點所造成的貢獻,也就是說,一個點可能有多條路徑作貢獻,所以這裏我們可以采用一個\(vector\)存下一個點的所有貢獻.然後在\(dfs\)統計差分數組的時候將一個點的貢獻全部加入差分數組中,其他的操作和之前寫部分分的方法是一樣的.

如果有過程不太理解的可以重新看一下各部分分的解法,最後整合的時候註意一下細節,這道題碼量感覺還是有點大的,要耐心打.

namespace pts100 {
    int ans[N], anss[N], anst[N], dep[N], gup[N][25], bs[N*2], bt[N];

    struct node { int pos, val; };

    vector <node> upds[N], updt[N];

    void dfs(int x, int f, int deep) {
        gup[x][0] = f, dep[x] = deep;
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs(e[i].to, x, deep+1);
    }

    void init() {
        for(int j = 1; j <= 23; j++)
            for(int i = 1; i <= n; i++) gup[i][j] = gup[gup[i][j-1]][j-1];
    }

    int lca(int a, int b) {
        if(dep[a] < dep[b]) swap(a, b);
        for(int i = 23; i >= 0; i--)
            if(dep[gup[a][i]] >= dep[b]) a = gup[a][i];
        if(a == b) return a;
        for(int i = 23; i >= 0; i--)
            if(gup[a][i] != gup[b][i]) a = gup[a][i], b = gup[b][i];
        return gup[a][0];
    }

    void dfs_s(int x, int f) {
        anss[x] -= bs[come[x]-dep[x]+N];
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs_s(e[i].to, x);
        for(int i = 0; i < upds[x].size(); i++)
            bs[upds[x][i].pos+N] += upds[x][i].val;
        anss[x] += bs[come[x]-dep[x]+N];
    }

    void dfs_t(int x, int f) {
        anst[x] -= bt[come[x]+dep[x]];
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs_t(e[i].to, x);
        for(int i = 0; i < updt[x].size(); i++)
            bt[updt[x][i].pos] += updt[x][i].val;
        anst[x] += bt[come[x]+dep[x]];
    }

    void work(path a[], int m) {
        dep[0] = -1, dfs(1, 0, 0), init();
        for(int i = 1; i <= m; i++) {
            int LCA, size;
            LCA = lca(a[i].x, a[i].y);
            size = dep[a[i].x]+dep[a[i].y]-dep[LCA]*2+1;
            if(a[i].x == a[i].y) {
                if(!come[a[i].x]) ans[a[i].x]++;
                continue;
            }
            updt[a[i].x].push_back((node) { dep[a[i].x], 1 }); // going up
            updt[gup[LCA][0]].push_back((node) { dep[a[i].x], -1 });
            upds[a[i].y].push_back((node) { size-dep[a[i].y]-1, 1 });
            upds[LCA].push_back((node) { size-dep[a[i].y]-1, -1 }); // going down
        }
        dfs_s(1, 0); dfs_t(1, 0);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]+anss[i]+anst[i]);
        printf("\n");
        exit(0);
    }
}

最後貼一個全部分分整合的代碼吧.

#include<bits/stdc++.h>
using namespace std;
const int N = 299998+5;

int n, m, last[N], come[N], ecnt = 0;

struct edge{
    int to, nex;
}e[N*2];

struct path{
    int x, y;
}a[N];

int gi(){
    int res = 0, f = 1; char i = getchar();
    while(i < '0' || i > '9'){ if(i == '-') f = -1; i = getchar(); }
    while(i >= '0' && i <= '9') res = res*10+i-'0', i = getchar();
    return res*f;
}

void add(int x, int y){
    e[++ecnt].to = y, e[ecnt].nex = last[x], last[x] = ecnt;
}

namespace pts25{
    int ans[N], dep[N], fa[N];

    void dfs(int x, int f, int deep){
        dep[x] = deep, fa[x] = f;
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != fa[x]) dfs(e[i].to, x, deep+1);
    }

    int lca(int x, int y){
        if(dep[x] < dep[y]) swap(x, y);
        while(dep[x] > dep[y]) x = fa[x];
        while(x != y) x = fa[x], y = fa[y];
        return x;
    }
    
    void check(int x, int y){
        int LCA = lca(x, y), size = dep[x]+dep[y]-dep[LCA]*2+1, cnt = 0, cnt1 = 0;
        for(; x != LCA; x = fa[x], cnt++, cnt1++)
            if(come[x] == cnt) ans[x]++;
        if(come[LCA] == cnt++) ans[LCA]++; size = size-cnt+cnt1;
        for(; y != LCA; y = fa[y], size--)
            if(come[y] == size) ans[y]++;
    }

    void work(){
        dfs(1, -1, 1);
        for(int i = 1; i <= m; i++) check(a[i].x, a[i].y);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]); printf("\n");
        exit(0);
    }
}

namespace pts_chain{
    int b[N], ans[N], cnt1 = 0, cnt2 = 0;

    struct node{
        int pos, upd, val;
    }upd1[N*2], upd2[N*2];

    bool cmp1(node a, node b){ return a.pos < b.pos; }
    bool cmp2(node a, node b){ return a.pos > b.pos; }
    
    void work(){
        for(int i = 1; i <= m; i++)
            if(a[i].x == a[i].y && come[a[i].x] == 0) ans[a[i].x]++;
        for(int i = 1; i <= m; i++){
            if(a[i].x < a[i].y){
            upd1[++cnt1] = (node){ a[i].x, a[i].x, 1 };
            upd1[++cnt1] = (node){ a[i].y+1, a[i].x, -1 };
            }
            if(a[i].x > a[i].y){
            upd2[++cnt2] = (node){ a[i].x, a[i].x, 1 };
            upd2[++cnt2] = (node){ a[i].y-1, a[i].x, -1 };
            }
        }
        sort(upd1+1, upd1+cnt1+1, cmp1), sort(upd2+1, upd2+cnt2+1, cmp2);
        
        for(int i = 1, pos = 1; i <= n; i++){
            for(; pos <= cnt1 && upd1[pos].pos <= i; pos++)
            b[upd1[pos].upd] += upd1[pos].val;
            ans[i] += b[i-come[i]];
        }
        for(int i = n, pos = 1; i >= 1; i--){
            for(; pos <= cnt2 && upd2[pos].pos >= i; pos++){
            cerr << upd2[pos].upd << endl;
            b[upd2[pos].upd] += upd2[pos].val;
            }
            ans[i] += b[i+come[i]];
        }
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]); printf("\n");
        exit(0);
    }
}

namespace pts_si1{
    int ans[N], dep[N];

    void dfs(int x, int f, int deep){
        dep[x] = deep;
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs(e[i].to, x, deep+1), ans[x] += ans[e[i].to];
    }
    
    void work(path a[], int m){
        for(int i = 1; i <= m; i++) ans[a[i].y]++;
        dfs(1, -1, 0);
        for(int i = 1; i <= n; i++){
            if(come[i] == dep[i]) printf("%d ", ans[i]);
            else printf("0 ");
        }
        exit(0);
    }
}

namespace pts_ti1{
    int ans[N], dep[N], b[N], upd[N];
    
    void dfs(int x, int f, int deep){
        dep[x] = deep, ans[x] -= b[come[x]+dep[x]];
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs(e[i].to, x, deep+1);
        b[dep[x]] += upd[x];
        ans[x] += b[come[x]+dep[x]];
    }
    
    void work(path a[], int m){
        for(int i = 1; i <= m; i++) upd[a[i].x]++;
        dfs(1, -1, 0);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]); printf("\n");
        exit(0);
    }
}

namespace pts100{
    int ans[N], anss[N], anst[N], dep[N], gup[N][25], bs[N*2], bt[N];

    struct node{ int pos, val; };
    
    vector <node> upds[N], updt[N];
    
    void dfs(int x, int f, int deep){
        gup[x][0] = f, dep[x] = deep;
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs(e[i].to, x, deep+1);
    }

    void init(){
        for(int j = 1; j <= 23; j++)
            for(int i = 1; i <= n; i++) gup[i][j] = gup[gup[i][j-1]][j-1];
    }
    
    int lca(int a, int b){
        if(dep[a] < dep[b]) swap(a, b);
        for(int i = 23; i >= 0; i--)
            if(dep[gup[a][i]] >= dep[b]) a = gup[a][i];
        if(a == b) return a;
        for(int i = 23; i >= 0; i--)
            if(gup[a][i] != gup[b][i]) a = gup[a][i], b = gup[b][i];
        return gup[a][0];
    }
    
    void dfs_s(int x, int f){
        anss[x] -= bs[come[x]-dep[x]+N];    
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs_s(e[i].to, x);
        for(int i = 0; i < upds[x].size(); i++)
            bs[upds[x][i].pos+N] += upds[x][i].val;
        anss[x] += bs[come[x]-dep[x]+N];
    }

    void dfs_t(int x, int f){
        anst[x] -= bt[come[x]+dep[x]];
        for(int i = last[x]; i; i = e[i].nex)
            if(e[i].to != f) dfs_t(e[i].to, x);
        for(int i = 0; i < updt[x].size(); i++)
            bt[updt[x][i].pos] += updt[x][i].val;
        anst[x] += bt[come[x]+dep[x]];
    }
    
    void work(path a[], int m){
        dep[0] = -1, dfs(1, 0, 0), init();
        for(int i = 1; i <= m; i++){
            int LCA, size; LCA = lca(a[i].x, a[i].y);
            size = dep[a[i].x]+dep[a[i].y]-dep[LCA]*2+1;
            if(a[i].x == a[i].y){ if(!come[a[i].x]) ans[a[i].x]++; continue; }
            updt[a[i].x].push_back((node){ dep[a[i].x], 1 }); // going up
            updt[gup[LCA][0]].push_back((node){ dep[a[i].x], -1 });
            upds[a[i].y].push_back((node){ size-dep[a[i].y]-1, 1 });
            upds[LCA].push_back((node){ size-dep[a[i].y]-1, -1 }); // going down
        }
        dfs_s(1, 0); dfs_t(1, 0);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]+anss[i]+anst[i]);
        printf("\n");
        exit(0);
    }
}

int main(){
    int x, y; n = gi(), m = gi();
    for(int i = 1; i < n; i++)
    x = gi(), y = gi(), add(x, y), add(y, x);
    for(int i = 1; i <= n; i++) come[i] = gi();
    for(int i = 1; i <= m; i++) a[i].x = gi(), a[i].y = gi();
    //if(n <= 993) pts25::work();
    //if(n == 99994) pts_chain::work();
    //if(n == 99995) pts_si1::work(a, m);
    //if(n == 99996) pts_ti1::work(a, m);
    pts100::work(a, m);
    return 0;
}

[洛谷P1600] 天天愛跑步