zoukankan      html  css  js  c++  java
  • NOIP2016 天天爱跑步

    题目地址

    这题各大OJ都有,放几个我交过的OJ里面这题的链接,下面的题面copy自BZOJ
    Luogu
    LOJ
    BZOJ

    Description

    小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。天天爱跑步是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

    这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。

    现在有个玩家,第个玩家的起点为Si ,终点为Ti 。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)

    小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道每个观察员会观察到多少人

    注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

    Input

    第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。

    接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。

    接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。

    接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。

    对于所有的数据,保证 1<=Si,Ti<=N,0<=Wj<=N

    Output

    输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。

    Sample Input

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

    Sample Output

    2 0 0 1 1 1

    HINT

    对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。

    对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。

    对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。

    对于4号点,玩家1被观察到,共1人被观察到。

    对于5号点,玩家1被观察到,共1人被观察到。

    对于6号点,玩家3被观察到,共1人被观察到

    Solution

    NOIP2016最难的一道题,这题花了我一天时间qwq,代码写了9kb(我每个部分分都写了)
    网上关于正解的题解很多,所以这里主要是介绍一下部分分做法,所有部分分做法都有讲到
    首先先看一下都有哪些部分分,各档部分分加起来足有80分,还是挺良心的qwq

    这篇题解在讲述时不会按照测试点顺序来讲,而是由易到难地讲解部分分做法,最后讲正解

    出于行文需要,做出以下规定:

    每个人走的路径下文直接称路径
    对于每个部分点,读入部分省去,只保留关键代码,文末有完整代码(下同)

    1. 测试点1,2(10分)

    这两个点保证每条路径的(S_i=T_i)
    很容易想到的就是只有这个点的(w_i=0)才能观察到这条路径。
    于是对于每条路径,判一下(w_i)是否为0,如果为0,则该点的答案+1

    void solve1() {
        for(int i = 1; i <= m; i ++) {
            int s = read(), t = read();
            if(w[s] == 0) ans[s] ++;
        }
        for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
    }
    

    2.测试点3,4(10分)

    这两个点保证所有点的(w)值为0,所以只有起点能观察到这条路径
    对每条路径的起点的答案+1即可

    void solve2() {
        for(int i = 1; i <= m; i ++) {
            int s = read(), t = read();
            ans[s] ++;
        }
        for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
    }
    

    3.测试点5(5分)

    这个点没有什么性质,但是(n,m<=1000)
    所以直接暴力跑lca判断即可

    void dfs1(int u) {
        siz[u] = 1;
    	for(int i = head[u]; i; i = e[i].nxt) {
            if(e[i].to == fa[u]) continue;
        	fa[e[i].to] = u;
        	dep[e[i].to] = dep[u] + 1;
            dfs1(e[i].to);
    	    siz[u] += siz[e[i].to];
    	}
    }
    
    int tim;
    void dfs2(int u, int topf) {
        top[u] = topf;
    	id[u] = ++ tim;
        int k = 0;
        for(int i = head[u]; i; i = e[i].nxt) {
        	if(e[i].to == fa[u]) continue;
            if(siz[e[i].to] > siz[k]) k = e[i].to;
        }
        if(!k) return;
        dfs2(k, topf);
        for(int i = head[u]; i; i = e[i].nxt) {
           if(e[i].to == fa[u] || e[i].to == k) continue;
           dfs2(e[i].to, e[i].to);
    	}
    }
    
    int lca(int x, int y) {
    	while(top[x] != top[y]) {
            if(dep[top[x]] < dep[top[y]]) swap(x, y);
        	x = fa[top[x]];
        }
    	if(dep[x] > dep[y]) swap(x, y);
        return x;
    }
    
    void solve() {
        dfs1(1);dfs2(1,1);
        for(int i = 1; i <= m; i ++) {
            int s = read(), t = read();
            int l = lca(s, t), tot = 0;
            for(int k = s; k != l; k = fa[k]) {
                if(w[k] == tot) ans[k] ++;
                ++tot;
            } 
            if(w[l] == tot) ans[l] ++;
            tot += dep[t] - dep[l]; 
            for(int k = t; k != l; k = fa[k]) {
                if(w[k] == tot) ans[k] ++; 
                --tot;
            }
        }
        for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
    }
    

    4.测试点9,10,11,12(20分)

    这几个测试点保证(S_i=0)
    这个性质有什么作用呢,经过一番思索不难想到,每条路径上的点,当且仅当(dep_i=w_i)时,这个点能观察到这条路径
    但是对于每条路径进行统计有点困难
    这里有两种方法
    1.树链剖分+线段树
    将会给出这种做法的代码
    实际上就是优化了一下暴力,给每条路径上的点都+1,利用树链剖分来实现
    最后遍历一遍整棵树,就可以得到答案了(注意只有(w_i=dep_i)的点的答案才是有效的)
    效率是(O(nlognlogn))

    namespace tree_chain {
        void dfs1(int u) {
            siz[u] = 1;
        	for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
        	    fa[e[i].to] = u;
            	dep[e[i].to] = dep[u] + 1;
                dfs1(e[i].to);
        	    siz[u] += siz[e[i].to];
        	}
        }
    
        int tim;
        void dfs2(int u, int topf) {
            top[u] = topf;
        	id[u] = ++ tim;
            int k = 0;
        	for(int i = head[u]; i; i = e[i].nxt) {
            	if(e[i].to == fa[u]) continue;
                if(siz[e[i].to] > siz[k]) k = e[i].to;
            }
            if(!k) return;
            dfs2(k, topf);
            for(int i = head[u]; i; i = e[i].nxt) {
     	       if(e[i].to == fa[u] || e[i].to == k) continue;
               dfs2(e[i].to, e[i].to);
     		}
        }
    
        int lca(int x, int y) {
        	while(top[x] != top[y]) {
        	    if(dep[top[x]] < dep[top[y]]) swap(x, y);
            	x = fa[top[x]];
        	}
        	if(dep[x] > dep[y]) swap(x, y);
        	return x;
        }
        
        struct tree {
        	int l, r, sum, add;
        }t[N<<2];
        #define mid ((l + r) >> 1)
        #define lc (rt << 1)
        #define rc (rt << 1 | 1)
        void build(int l, int r, int rt) {
            t[rt].l = l; t[rt].r = r;
            if(l == r) return;
            build(l, mid, lc); build(mid + 1, r, rc);
        }
        void pushup(int rt) {t[rt].sum = t[lc].sum + t[rc].sum;}
        #define l t[rt].l
        #define r t[rt].r
        void pushdown(int ln, int rn, int rt) {
            if(t[rt].add) {
                int &x = t[rt].add;
                t[lc].sum += x * ln; t[rc].sum += x * rn;
                t[lc].add += x; t[rc].add += x;
                x = 0;
            }
        }
        void upd(int L, int R, int c, int rt) {
            if(L <= l && r <= R) {
                t[rt].sum += c * (r - l + 1);
                t[rt].add += c;
                return;
            }
            pushdown(mid - l + 1, r - mid, rt);
            if(L <= mid) upd(L, R, c, lc);
            if(R > mid) upd(L, R, c, rc);
            pushup(rt);
        }
        int query(int L, int R, int rt) {
            if(L <= l && r <= R) return t[rt].sum;
            pushdown(mid - l + 1, r - mid, rt); int ans = 0;
            if(L <= mid) ans += query(L, R, lc);
            if(R > mid) ans += query(L, R, rc);
            return ans;
        }
        #undef lc
        #undef rc
        #undef l
        #undef r
        #undef mid
        
        void add(int x, int y) {
            while(top[x] != top[y]) {
                if(dep[top[x]] < dep[top[y]]) swap(x, y);
                upd(id[top[x]], id[x], 1, 1);
                x = fa[top[x]];
            }
            if(dep[x] > dep[y]) swap(x, y);
            upd(id[x], id[y], 1, 1);
        }
    }
    using namespace tree_chain;
    
    namespace pts_3 {
        void solve() {
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                add(s, t);
            }
            for(int i = 1; i <= n; i ++) {
                if(w[i] == dep[i]) {
                    ans[i] = query(id[i], id[i], 1);
                }
            }   
            for(int i = 1; i <= n; i ++) {
                out(ans[i]), putchar(' ');
            }
        }
    }
    

    2.树上差分
    这是一个我在思路逐步接近正解之后才明白的做法(其实每一档都在暗示正解)
    我们可以把这个问题转化为贡献问题,我们把每条路径的起点+1,终点-1(在这里终点-1其实可以不用)
    那么这个点在多少条路径里面也就直接查询子树和就可以了
    这样我们就把这个问题转化为了查询子树贡献和的问题,所有点的子树贡献可以(O(n))遍历得到答案
    总的复杂度为(O(n))
    但是我没有写这种做法qwq

    5.测试点13,14,15,16(20分)

    这些测试点保证(T_i=1)。所有路径的终点都在1,考虑这个性质有啥用
    对于一个路径上的点i,它能观察到这条路径的条件是(dep_S-dep_i=w_i)
    移项一下,就变成了(dep_S=w_i+dep_i)
    考虑一下我们在上面说过的树上差分做法
    事实上是差不多的,标记所有路径的起点,最后遍历一次子树,用桶储存每个深度各有几个起点
    那么对于一个点,它的答案就是子树内(dep_S=w_i+dep_i)(S)的数量(注意要在递归进子树时记录下桶内的值,回溯之后再记录一下,两值之差才是答案)

    namespace pts_4 {
        // t = 1
        // dep[s]-dep[i]=w[i]则有贡献
        // 即 dep[s]=w[i]+dep[i]
        void dfs(int u) {
            if(qwq[u]) total[dep[u]] += qwq[u];
            int t = total[f[u]];
            for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
                dfs(e[i].to);
            }
            ans[u] = total[f[u]] - t;
        } 
        void solve() {
            for(int i = 1; i <= n; i ++) f[i] = w[i] + dep[i];
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                qwq[s]++;
            }
            dfs(1);
            for(int i = 1; i <= n; i ++) {
                out(ans[i]), putchar(' ');
            }
        }
    }
    

    6.测试点6,7,8(15分)

    这是一条链。
    有什么用呢?在做完上面的(S_i=1)(T_i=1)的部分分后,再思索一下大概就能想出来链的做法了。
    我们可以把这条链放成横的,那么就变成了一段序列。
    那么路径的方向显然就有从左到右,从右到左两种。
    我们可以分类讨论一下,先说从左到右的。
    因为路径每秒前进一个单位长度,所以不难想到的是对于一条路径上的点i,它能被观察到的话,一定满足(dep_i-dep_s=w_i)
    移项一下,(dep_i-w_i=dep_s)
    是不是莫名熟悉!事实上和上面的两个部分分做法并没有什么本质区别!
    但是链比上面两个部分分做法难的是,它的起点和终点都不是固定的
    所以我们并不能像上面那样简单的去处理,但是思路可以继续沿用
    起点+1,终点-1!树上差分!
    我们考虑维护一个类似于桶的数组,设为(A[i]),它表示经过i点而且还没有结束的路径的条数,那么显然,每个点i的答案就是(A[dep_i+w_i])
    如何维护这个数组?可以利用vector来实现对于路径的维护,这个可以参考一下正解的代码,我当时做这个部分点时并不是这么做的。
    而从右到左的情况是完全类似的。式子变为(dep_i+w_i=dep_s)即可

    另外一种方法:
    从上面的分析,我们可以知道,对于一个点(i),只有两种路径会对它产生贡献
    1.起点在(dep_i-w_i),且终点在(i)右边
    2.起点在(dep_i+w_i),且终点在(i)左边
    也就是说,终点其实只是一个判定条件!
    那么我们可以用类似邻接表存图的方法,将起点和终点连边!
    我们知道,邻接表的遍历

    for(int i = head[u]; i; i = e[i].nxt)
    

    就是遍历所有与它相连的边
    所以我们这样连边然后遍历了一次之后也就可以找出所有起点在(head[u])的路径的终点了!一一判定即可
    复杂度是(O(n))的(每条路径会被遍历一次,每个点会被遍历一次,n,m同阶,所以复杂度为(O(n))
    实际代码中使用vector模拟邻接表。

    namespace pts_5 {
    //链的情况
    //要么s>t 要么s <= t
    //考虑s <= t
    //对于s -> t路径上的i,只有i - s = w[i]时才有贡献
    //i-w[i]=s
    //设f[i]=i-w[i]
    //即s=f[i]时,才有贡献
    //s > t同理 
    //所以对于一个点i,对它有贡献的点一定是起点为i-w[i]/i+w[i]且终点过i的路径的个数 
        vector<int> G[N];
        void solve() {
            for(int i = 1; i <= m; i ++) {
                int S = read(), T = read();
                G[S].push_back(T);
            }
            for(int i = 1; i <= n; i ++) {
                int tot = 0;
                if(i - w[i] > 0)
                    for(int j = 0, len = G[i - w[i]].size(); j < len; j ++) {
                        if(G[i - w[i]][j] >= i) tot ++;
                    }
                if(i + w[i] <= n)
                    for(int j = 0, len = G[i + w[i]].size(); j < len; j ++) {
                        if(G[i + w[i]][j] <= i) tot ++;
                    }
                ans[i] = tot;
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
    }
    

    那么至此,我们已经讲完所有的部分分。部分分还是给的很足的。
    可以发现,所有的部分分(除了暴力)都在把我们往一个方向带,也就是正解的方向!(赤裸裸的暗示)
    虽然我写的时候跑歪了...是都写了,但是其中45分都是用奇奇怪怪的方法来拿的,对想正解没有啥帮助(树剖和模拟邻接表)

    7.测试点17,18,19,20(20分)

    就是正解了。
    可以发现其实链的做法很接近正解了。各档部分分也在暗示着正解的做法。
    在结合一下(S_i=1,T_i=1)的做法以及平时做到的关于树的题目的常见套路
    可以猜出来的是,我们要拆路径
    拆成(S->LCA(S,T),LCA(S,T)->T)
    这样每一部分其实就对应了链的做法了,处理的时候再套上(S_i=1和T_i=1)的做法,就可以了
    具体做法大概是这样的:
    我们先讨论(S->LCA(S,T))这条路径
    首先对应链做法,我们可以得到式子(dep_s=dep_i-w_i)
    那么我们再套上处理先前的树上差分做法(起点+1终点-1)
    就可以处理了
    至于(LCA(S,T)->T)这一段。
    我们可以推出的式子是(dep_T-dep_i=len-w_i)
    (dep_T-len=dep_i-w_i)
    (len为该条路径长度)
    用类似的做法即可。
    但是注意这样子等式两边都可能是负数,所以整体右移个(3*10^5)即可
    复杂度是(O(nlogn))的(复杂度瓶颈在找LCA,所以其实可以用tarjan做到(O(n+m))

    具体处理上有一些要注意的就是
    1.可以利用vector来处理(这样数组好开)
    2.处理顺序要注意,要先给起点加上贡献,然后再算出这个点的答案,最后再减去终点的贡献
    3.拆出来的链分开处理

    代码(这个是完整的代码)

    #include <bits/stdc++.h>
    
    #define ll long long
    #define inf 0x3f3f3f3f
    #define il inline
    
    namespace io {
    
        #define in(a) a=read()
        #define out(a) write(a)
        #define outn(a) out(a),putchar('
    ')
    
        #define I_int int
        inline I_int read() {
            I_int x = 0, f = 1; char c = getchar();
            while( c < '0' || c > '9' ) { if( c == '-' ) f = -1 ; c = getchar() ; }
            while( c >= '0' && c <= '9' ) { x = x * 10 + c - '0' ; c = getchar() ; }
            return x * f ;
        }
        char F[ 200 ] ;
        inline void write( I_int x ) {
            if( x == 0 ) { putchar( '0' ) ; return ; }
            I_int tmp = x > 0 ? x : -x ;
            if( x < 0 ) putchar( '-' ) ;
            int cnt = 0 ;
            while( tmp > 0 ) {
                F[ cnt ++ ] = tmp % 10 + '0' ;
                tmp /= 10 ;
            }
            while( cnt > 0 ) putchar( F[ -- cnt ] ) ;
        }
        #undef I_int
    
    }
    using namespace io ;
    
    using namespace std ;
    
    #define N 300010
    
    int siz[N], dep[N], top[N], fa[N], id[N];
    int n, m, f[N], qwq[N], total[N * 2], QAQ[N * 3];
    int cnt, head[N], w[N];
    int ans[N];
    struct edge {
        int to, nxt;
    }e[N<<1];
    
    void ins(int u, int v) {
        e[++cnt] = (edge) {v, head[u]};
        head[u] = cnt;
    }
    
    namespace tree_chain {
        void dfs1(int u) {
            siz[u] = 1;
        	for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
        	    fa[e[i].to] = u;
            	dep[e[i].to] = dep[u] + 1;
                dfs1(e[i].to);
        	    siz[u] += siz[e[i].to];
        	}
        }
    
        int tim;
        void dfs2(int u, int topf) {
            top[u] = topf;
        	id[u] = ++ tim;
            int k = 0;
        	for(int i = head[u]; i; i = e[i].nxt) {
            	if(e[i].to == fa[u]) continue;
                if(siz[e[i].to] > siz[k]) k = e[i].to;
            }
            if(!k) return;
            dfs2(k, topf);
            for(int i = head[u]; i; i = e[i].nxt) {
     	       if(e[i].to == fa[u] || e[i].to == k) continue;
               dfs2(e[i].to, e[i].to);
     		}
        }
    
        int lca(int x, int y) {
        	while(top[x] != top[y]) {
        	    if(dep[top[x]] < dep[top[y]]) swap(x, y);
            	x = fa[top[x]];
        	}
        	if(dep[x] > dep[y]) swap(x, y);
        	return x;
        }
        struct tree {
        	int l, r, sum, add;
        }t[N<<2];
        #define mid ((l + r) >> 1)
        #define lc (rt << 1)
        #define rc (rt << 1 | 1)
        void build(int l, int r, int rt) {
            t[rt].l = l; t[rt].r = r;
            if(l == r) return;
            build(l, mid, lc); build(mid + 1, r, rc);
        }
        void pushup(int rt) {t[rt].sum = t[lc].sum + t[rc].sum;}
        #define l t[rt].l
        #define r t[rt].r
        void pushdown(int ln, int rn, int rt) {
            if(t[rt].add) {
                int &x = t[rt].add;
                t[lc].sum += x * ln; t[rc].sum += x * rn;
                t[lc].add += x; t[rc].add += x;
                x = 0;
            }
        }
        void upd(int L, int R, int c, int rt) {
            if(L <= l && r <= R) {
                t[rt].sum += c * (r - l + 1);
                t[rt].add += c;
                return;
            }
            pushdown(mid - l + 1, r - mid, rt);
            if(L <= mid) upd(L, R, c, lc);
            if(R > mid) upd(L, R, c, rc);
            pushup(rt);
        }
        int query(int L, int R, int rt) {
            if(L <= l && r <= R) return t[rt].sum;
            pushdown(mid - l + 1, r - mid, rt); int ans = 0;
            if(L <= mid) ans += query(L, R, lc);
            if(R > mid) ans += query(L, R, rc);
            return ans;
        }
        #undef lc
        #undef rc
        #undef l
        #undef r
        #undef mid
    }
    using namespace tree_chain;
    
    namespace pts_6 {
        struct ques {
            int s, t, l, len;
        } q[N];
        vector<int>v1[N*2], v2[N*2], v3[N * 2]; 
        int Max_dep = 0;
        //对于u->lca(u,v)
        //同链做法 
        void dfs_1(int u) {
            int now = w[u] + dep[u] + 300000, t = total[now]; 
            for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
                dfs_1(e[i].to);
            }
            total[dep[u] + 300000] += qwq[u];
            ans[u] += total[now] - t;
            for(int i = 0, len = v3[u].size(); i < len; i ++) total[dep[v3[u][i]] + 300000] --;
        }
        //对于lca(u,v) -> v
        //可得式子:
        //dep[t]-dep[i] = len - w[i]
        //移项得
        //dep[t] - len = dep[i] - w[i] 
        void dfs_2(int u) {
            int now = w[u] - dep[u] + 300000;
            int t = QAQ[now];
            for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
                dfs_2(e[i].to);
            }
            for(int i = 0, len = v2[u].size(); i < len; i ++) QAQ[300000 + v2[u][i]] ++;
            ans[u] += QAQ[now] - t;
            for(int i = 0, len = v1[u].size(); i < len; i ++) QAQ[300000 + v1[u][i]] --;
        }
    
        void solve() {
            for(int i = 1; i <= n; i ++) Max_dep = max(Max_dep, dep[i]);
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read(), l = lca(s, t);
                int len = dep[s] + dep[t] - 2 * dep[l];
                q[i] = (ques) {s, t, l, len};
                v3[l].push_back(s); qwq[s] ++;
            }
            dfs_1(1);
            for(int i = 1; i <= m; i ++) {
                int t = q[i].t, l = q[i].l, len = q[i].len;
                v1[l].push_back(len - dep[t]);
                v2[t].push_back(len - dep[t]);
            }
            memset(QAQ,0,sizeof(QAQ));
            dfs_2(1);
            for(int i = 1; i <= m; i ++) {
                if(dep[q[i].s] - dep[q[i].l] == w[q[i].l])
                    ans[q[i].l] --;
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
    }
    
    int main() {
        n = read(), m = read();
        for(int i = 1; i < n; i ++) {
            int u = read(), v = read();
            ins(u, v), ins(v, u);
        }
        for(int i = 1; i <= n; i ++) w[i] = read();
        dfs1(1), dfs2(1, 1); 
        pts_6::solve();
    }
    

    (实际上树剖那里只保留三个就好不过我懒得删了qwq)

    然后放上长达9kb的有所有部分分做法的代码

    #include <bits/stdc++.h>
    
    #define ll long long
    #define inf 0x3f3f3f3f
    #define il inline
    
    namespace io {
    
        #define in(a) a=read()
        #define out(a) write(a)
        #define outn(a) out(a),putchar('
    ')
    
        #define I_int int
        inline I_int read() {
            I_int x = 0, f = 1; char c = getchar();
            while( c < '0' || c > '9' ) { if( c == '-' ) f = -1 ; c = getchar() ; }
            while( c >= '0' && c <= '9' ) { x = x * 10 + c - '0' ; c = getchar() ; }
            return x * f ;
        }
        char F[ 200 ] ;
        inline void write( I_int x ) {
            if( x == 0 ) { putchar( '0' ) ; return ; }
            I_int tmp = x > 0 ? x : -x ;
            if( x < 0 ) putchar( '-' ) ;
            int cnt = 0 ;
            while( tmp > 0 ) {
                F[ cnt ++ ] = tmp % 10 + '0' ;
                tmp /= 10 ;
            }
            while( cnt > 0 ) putchar( F[ -- cnt ] ) ;
        }
        #undef I_int
    
    }
    using namespace io ;
    
    using namespace std ;
    
    #define N 300010
    
    int siz[N], dep[N], top[N], fa[N], id[N];
    int n, m, f[N], qwq[N], total[N * 2], QAQ[N * 3];
    int cnt, head[N], w[N];
    int ans[N];
    struct edge {
        int to, nxt;
    }e[N<<1];
    
    void ins(int u, int v) {
        e[++cnt] = (edge) {v, head[u]};
        head[u] = cnt;
    }
    
    namespace pts_1 {
        void solve1() {
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                if(w[s] == 0) ans[s] ++;
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
        void solve2() {
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                ans[s] ++;
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
    }
    
    namespace tree_chain {
        void dfs1(int u) {
            siz[u] = 1;
        	for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
        	    fa[e[i].to] = u;
            	dep[e[i].to] = dep[u] + 1;
                dfs1(e[i].to);
        	    siz[u] += siz[e[i].to];
        	}
        }
    
        int tim;
        void dfs2(int u, int topf) {
            top[u] = topf;
        	id[u] = ++ tim;
            int k = 0;
        	for(int i = head[u]; i; i = e[i].nxt) {
            	if(e[i].to == fa[u]) continue;
                if(siz[e[i].to] > siz[k]) k = e[i].to;
            }
            if(!k) return;
            dfs2(k, topf);
            for(int i = head[u]; i; i = e[i].nxt) {
     	       if(e[i].to == fa[u] || e[i].to == k) continue;
               dfs2(e[i].to, e[i].to);
     		}
        }
    
        int lca(int x, int y) {
        	while(top[x] != top[y]) {
        	    if(dep[top[x]] < dep[top[y]]) swap(x, y);
            	x = fa[top[x]];
        	}
        	if(dep[x] > dep[y]) swap(x, y);
        	return x;
        }
        struct tree {
        	int l, r, sum, add;
        }t[N<<2];
        #define mid ((l + r) >> 1)
        #define lc (rt << 1)
        #define rc (rt << 1 | 1)
        void build(int l, int r, int rt) {
            t[rt].l = l; t[rt].r = r;
            if(l == r) return;
            build(l, mid, lc); build(mid + 1, r, rc);
        }
        void pushup(int rt) {t[rt].sum = t[lc].sum + t[rc].sum;}
        #define l t[rt].l
        #define r t[rt].r
        void pushdown(int ln, int rn, int rt) {
            if(t[rt].add) {
                int &x = t[rt].add;
                t[lc].sum += x * ln; t[rc].sum += x * rn;
                t[lc].add += x; t[rc].add += x;
                x = 0;
            }
        }
        void upd(int L, int R, int c, int rt) {
            if(L <= l && r <= R) {
                t[rt].sum += c * (r - l + 1);
                t[rt].add += c;
                return;
            }
            pushdown(mid - l + 1, r - mid, rt);
            if(L <= mid) upd(L, R, c, lc);
            if(R > mid) upd(L, R, c, rc);
            pushup(rt);
        }
        int query(int L, int R, int rt) {
            if(L <= l && r <= R) return t[rt].sum;
            pushdown(mid - l + 1, r - mid, rt); int ans = 0;
            if(L <= mid) ans += query(L, R, lc);
            if(R > mid) ans += query(L, R, rc);
            return ans;
        }
        #undef lc
        #undef rc
        #undef l
        #undef r
        #undef mid
    }
    using namespace tree_chain;
    
    namespace pts_2 {
        void solve() {
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                int l = lca(s, t), tot = 0;
                for(int k = s; k != l; k = fa[k]) {
                    if(w[k] == tot) ans[k] ++;
                    ++tot;
                } 
                if(w[l] == tot) ans[l] ++;
                tot += dep[t] - dep[l]; 
                for(int k = t; k != l; k = fa[k]) {
                    if(w[k] == tot) ans[k] ++; 
                    --tot;
                }
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
    }
    
    void add(int x, int y) {
        while(top[x] != top[y]) {
            if(dep[top[x]] < dep[top[y]]) swap(x, y);
            upd(id[top[x]], id[x], 1, 1);
            x = fa[top[x]];
        }
        if(dep[x] > dep[y]) swap(x, y);
        upd(id[x], id[y], 1, 1);
    }
    
    namespace pts_3 {
        void solve() {
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                add(s, t);
            }
            for(int i = 1; i <= n; i ++) {
                if(w[i] == dep[i]) {
                    ans[i] = query(id[i], id[i], 1);
                }
            }   
            for(int i = 1; i <= n; i ++) {
                out(ans[i]), putchar(' ');
            }
        }
    }
    
    namespace pts_5 {
    //链的情况
    //要么s>t 要么s <= t
    //考虑s <= t
    //对于s -> t路径上的i,只有i - s = w[i]时才有贡献
    //i-w[i]=s
    //设f[i]=i-w[i]
    //即s=f[i]时,才有贡献
    //s > t同理 
    //所以对于一个点i,对它有贡献的点一定是起点为i-w[i]/i+w[i]且终点过i的路径的个数 
        vector<int> G[N];
        void solve() {
            for(int i = 1; i <= m; i ++) {
                int S = read(), T = read();
                G[S].push_back(T);
            }
            for(int i = 1; i <= n; i ++) {
                int tot = 0;
                if(i - w[i] > 0)
                    for(int j = 0, len = G[i - w[i]].size(); j < len; j ++) {
                        if(G[i - w[i]][j] >= i) tot ++;
                    }
                if(i + w[i] <= n)
                    for(int j = 0, len = G[i + w[i]].size(); j < len; j ++) {
                        if(G[i + w[i]][j] <= i) tot ++;
                    }
                ans[i] = tot;
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
    }
    
    namespace pts_4 {
        // t = 1
        // dep[s]-dep[i]=w[i]则有贡献
        // 即 dep[s]=w[i]+dep[i]
        void dfs(int u) {
            if(qwq[u]) total[dep[u]] += qwq[u];
            int t = total[f[u]];
            for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
                dfs(e[i].to);
            }
            ans[u] = total[f[u]] - t;
        } 
        void solve() {
            for(int i = 1; i <= n; i ++) f[i] = w[i] + dep[i];
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read();
                qwq[s]++;
            }
            dfs(1);
            for(int i = 1; i <= n; i ++) {
                out(ans[i]), putchar(' ');
            }
        }
    }
    
    namespace pts_6 {
        struct ques {
            int s, t, l, len;
        } q[N];
        vector<int>v1[N*2], v2[N*2], v3[N * 2]; 
        int Max_dep = 0;
        //对于u->lca(u,v)
        //同链做法 
        void dfs_1(int u) {
            int now = w[u] + dep[u] + 300000, t = total[now]; 
            for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
                dfs_1(e[i].to);
            }
            total[dep[u] + 300000] += qwq[u];
            ans[u] += total[now] - t;
            for(int i = 0, len = v3[u].size(); i < len; i ++) total[dep[v3[u][i]] + 300000] --;
        }
        //对于lca(u,v) -> v
        //可得式子:
        //dep[t]-dep[i] = len - w[i]
        //移项得
        //dep[t] - len = dep[i] - w[i] 
        void dfs_2(int u) {
            int now = w[u] - dep[u] + 300000;
            int t = QAQ[now];
            for(int i = head[u]; i; i = e[i].nxt) {
                if(e[i].to == fa[u]) continue;
                dfs_2(e[i].to);
            }
            for(int i = 0, len = v2[u].size(); i < len; i ++) QAQ[300000 + v2[u][i]] ++;
            ans[u] += QAQ[now] - t;
            for(int i = 0, len = v1[u].size(); i < len; i ++) QAQ[300000 + v1[u][i]] --;
        }
    
        void solve() {
            for(int i = 1; i <= n; i ++) Max_dep = max(Max_dep, dep[i]);
            for(int i = 1; i <= m; i ++) {
                int s = read(), t = read(), l = lca(s, t);
                int len = dep[s] + dep[t] - 2 * dep[l];
                q[i] = (ques) {s, t, l, len};
                v3[l].push_back(s); qwq[s] ++;
            }
            dfs_1(1);
            for(int i = 1; i <= m; i ++) {
                int t = q[i].t, l = q[i].l, len = q[i].len;
                v1[l].push_back(len - dep[t]);
                v2[t].push_back(len - dep[t]);
            }
            memset(QAQ,0,sizeof(QAQ));
            dfs_2(1);
            for(int i = 1; i <= m; i ++) {
                if(dep[q[i].s] - dep[q[i].l] == w[q[i].l])
                    ans[q[i].l] --;
            }
            for(int i = 1; i <= n; i ++) out(ans[i]), putchar(' ');
        }
    }
    
    int main() {
        n = read(), m = read();
        for(int i = 1; i < n; i ++) {
            int u = read(), v = read();
            ins(u, v), ins(v, u);
        }
        for(int i = 1; i <= n; i ++) w[i] = read();
        if(n == 991 && m == 991) {pts_1::solve1(); return 0;}
        if(n == 992 && m == 992) {pts_1::solve2(); return 0;}
        dfs1(1), dfs2(1, 1); 
        if(n == 993 && m == 993) {pts_2::solve(); return 0;}
        if(n == 99995 && m == 99995) {build(1, n, 1); pts_3::solve(); return 0;}
        if(n == 99996 && m == 99996) {pts_4::solve(); return 0;}
        if(n == 99994 && m == 99994) {pts_5::solve(); return 0;}
        pts_6::solve();
    }
    
  • 相关阅读:
    haoop 断电后导致block文件损坏
    笔记本上小固态硬盘的使用
    centos 7安装部署docker
    用msbuild构建应用
    清风注解-Swift程序设计语言:Point11~15
    清风注解-Swift程序设计语言:Point6~10
    清风注解-Swift程序设计语言
    享骑电单车投诉
    yaf不输出致命错误(Fatal error)的原因(之一)
    如何面试 PHP 工程师?
  • 原文地址:https://www.cnblogs.com/henry-1202/p/10176610.html
Copyright © 2011-2022 走看看