• [NOIp2016]天天爱跑步


    Description

    BZOJ4719
    Luogu1600
    给定一棵树和若干玩家,玩家在树上的路径中跑,移动速度为(1mbox{边}/s)。求某一时刻经过某个点的玩家个数。

    Solution

    这个题由于比较难,我们要看部分分做题。

    Part 1

    测试点1,2,3,4。
    起点等于终点或(w_i=0),这种点就直接讨论一下水过去就行。

    测试点5。
    数据小,直接(O(n^2))暴力模拟也可以过。

    Part 2

    测试点6,7,8。
    链上的情况比较复杂。
    考虑哪些玩家(x)会对某个点(i)产生贡献,这需要满足起点在(i)一侧且(dis_{i,x} = w_i),即(x=ipm w_i),终点在(i)的另一侧。
    我们可以借助差分的思想,从左向右和从右向左各扫一遍,扫到起点时将(ans_{S_i})自增,扫到终点时再减回来。这样扫到一个点时(ans_{ipm w_i})就是答案。

    测试点9,10,11,12。
    起点是1,只有(w_i=deep_i)的点才有答案,答案是子树中的玩家个数。

    测试点13,14,15,16。
    这次是终点为1了,和起点为1很像,只有(w_i + deep_i = S_j)(S_j)(i)的子树中的路径(j)会对点(i)有贡献,运用链上的想法,记录一下某个节点递归进入子树前后的(ans)的变化量即为该点答案。

    Part 3

    综合考虑Part 2中的思路。将路径拆分为向上和向下两部分,分别类比起点为1和终点为1时的情况求解。需要注意的是如果(lca)被一条路径贡献了两次,需要将多余的贡献删去。

    Code

    #include <cstdio>
    
    const int N = 3e6 + 10;
    const int M = 2*N + 10;
    
    int n, m;
    int hd[N], to[M], nxt[M], cnt;
    int dis[N], w[N], sz[N], son[N], top[N], fa[N];
    int S[N], T[N];
    int ptot;
    struct Path {
        int s, t, up, ns, nt;
    } p[M];
    int pret[M];
    int uac[M], dac[M];
    int ps[N], pt[N];
    int mark[N], ans[N];
    
    inline void adde(int x, int y) {
        cnt++; to[cnt] = y; nxt[cnt] = hd[x];
        hd[x] = cnt;
    }
    
    void dfs(int x, int f) {
    	dis[x] = dis[f] + 1;
    	fa[x] = f; sz[x] = 1;
        for (int i = hd[x]; i; i = nxt[i]) if (to[i] != f) {
            dfs(to[i], x);
            sz[x] += sz[to[i]];
            if (sz[son[x]] < sz[to[i]]) son[x] = to[i];
        }
    }
    void rebuild(int x, int f) {
        top[x] = f;
        if (son[x]) rebuild(son[x], f);
        for (int i = hd[x]; i; i = nxt[i]) if (to[i] != son[x] && to[i] != fa[x]) {
            rebuild(to[i], to[i]);
        }
    } 
    
    int lca(int x, int y) {
        while (top[x] != top[y]) {
            if (dis[fa[top[x]]] > dis[fa[top[y]]]) x = fa[top[x]];
            else y = fa[top[y]];
        }
        return dis[x] < dis[y] ? x : y;
    }
    
    void addp(int s, int t, int up) {
        ptot++;
        p[ptot].s = s; p[ptot].t = t; p[ptot].up = up;
        p[ptot].ns = ps[s]; ps[s] = ptot;
        p[ptot].nt = pt[t]; pt[t] = ptot;
    }
    
    void calc(int x, int f) {
        int uOld = uac[w[x]+dis[x]], dOld = dac[w[x]-dis[x]+n];
    // 统计该点的贡献	
        for (int i = ps[x]; i; i = p[i].ns) 
    		if (p[i].up) uac[dis[p[i].s]]++;
        for (int i = pt[x]; i; i = p[i].nt) 
    		if (!p[i].up) dac[pret[i] - dis[p[i].s] + n]++;
    // 递归计算后将差量作为答案		
        for (int i = hd[x]; i; i = nxt[i]) if (to[i] != f) calc(to[i], x);
        ans[x] = uac[w[x]+dis[x]] - uOld + dac[w[x]-dis[x]+n] - dOld - mark[x];
    // 去掉不应继续上传的路径的贡献	
        for (int i = ps[x]; i; i = p[i].ns) 
    		if (!p[i].up) dac[pret[i] - dis[p[i].s] + n]--;
        for (int i = pt[x]; i; i = p[i].nt) 
    		if (p[i].up) uac[dis[p[i].s]]--;
    }
    
    int main() {
    // 读入
    	scanf("%d%d", &n, &m);
        for (int i = 1, x, y; i < n; ++i) {
            scanf("%d%d", &x, &y);
            adde(x, y);
            adde(y, x);
        }
        for (int i = 1; i <= n; ++i) scanf("%d", &w[i]);
        for (int i = 1; i <= m; ++i) {
            scanf("%d%d", &S[i], &T[i]);
        }
    // 树剖求LCA	
    	dfs(1, 0); rebuild(1, 1);
    // 切分路径
        for (int i = 1; i <= m; ++i) {
            int Lca = lca(S[i], T[i]);
            if (Lca == S[i]) {
                addp(S[i], T[i], 0);
            } else if (Lca == T[i]) {
                addp(S[i], T[i], 1);
            } else {
                addp(S[i], Lca, 1);
                addp(Lca, T[i], 0);
                pret[ptot] = pret[ptot-1] = dis[S[i]] - dis[Lca];
                if (dis[S[i]] == w[Lca] + dis[Lca]) mark[Lca]++; // 去重
            }
        } 
    // dfs计算	
        calc(1, 0);
    	
    	for (int i = 1; i <= n; ++i) printf("%d ", ans[i]); puts("");
    	return 0;
    } 
    
  • 相关阅读:
    图解MBR分区无损转换GPT分区+UEFI引导安装WIN8.1 分类: Windows-嵌入式 生活百科 2015-06-26 11:57 475人阅读 评论(2) 收藏
    使用EFI引导从硬盘(U盘)安装Win7的图文教程 分类: Windows-嵌入式 生活百科 2015-06-25 16:40 320人阅读 评论(0) 收藏
    嵌入式Linux基于framebuffer的jpeg格式本地LCD屏显示 分类: TI-AM335X arm-linux-Ubuntu 2015-06-24 10:41 338人阅读 评论(0) 收藏
    Linux开机启动图片修改 分类: arm-linux-Ubuntu TI-AM335X 2015-06-19 16:12 390人阅读 评论(0) 收藏
    Linux下LCD 10分钟自动关屏的问题总结 分类: TI-AM335X arm-linux-Ubuntu 2015-06-19 15:43 369人阅读 评论(1) 收藏
    am335x在ubuntu下使用StarterWare编写裸机程序并在CCS中用Jlink调试 分类: TI-AM335X 2015-06-18 11:27 276人阅读 评论(0) 收藏
    IE 首页锁定
    LevelDb 资料整理
    c++ windows error C2662 C2663
    2018 ACM 网络选拔赛 青岛赛区
  • 原文地址:https://www.cnblogs.com/wyxwyx/p/noip201612.html
Copyright © 2020-2023  润新知