• [NOIP2016提高组]天天爱跑步


    题目:洛谷P1600、BZOJ4719、Vijos P2004、UOJ#261。

    题目大意:一些人在一棵树上,每秒移动一个单位,从起点移动到终点。每个节点上有一个观测员,第i个能观测到w[i]秒刚好到这个节点的人。问每个观测员能观测到几个人。

    解题思路:NOIP2016提高最难的一题啊!虽说80分很容易拿,但想AC却真的很难!w(゚Д゚)w

    做法基本就这么两种:①树链剖分;②LCA+树上差分。

    树剖我掌握得并不熟练(我仅用它写过LCA而且已经忘了),所以我只能用②了。

    先讲思路。首先,对于每个人走的路径s->t,可拆分为s->lca(s,t)和lca(s,t)->t。我们对其分别考虑。

    对于s->lca(s,t)之间任何一个点i,它能观测到当前这个人必须满足$deep[s]-w[i]=deep[i]$(其中deep[i]表示节点i到根节点的深度,下同),移项得$deep[s]=deep[i]+w[i]$,这样左边只和s有关,右边只和i有关。

    再来考虑lca(s,t)->t之间任何一个点i,它能观测到当前这个人必须满足$deep[t]-deep[i]=len-w[i]$(len表示当前这个人的路径的总长度),移项得$deep[t]-len=deep[i]-w[i]$,这样左边只和t有关,右边只和i有关。

    然后分别对两种路径进行差分。dfs遍历树,然后当前节点为i,我们用一个桶(tong)统计。tong的下标即为等式右边的表达式(用i即可算出),记录的即为等式左边的表达式(统计答案)。

    由于$deep[t]-len=deep[i]-w[i]$中,等式左、右可能出现负数,只需把桶的下标统一往后移300000即可(Pascal请忽略)。

    最后,如果一个人在lca上被统计到,那么它在s->lca(s,t)和lca(s,t)->t中都被统计了一遍,需要减去。

    时间复杂度:Tarjan求LCA则$O(n+m)$,倍增等求LCA则$O(mlog_2 n)$,差分是$O(n)$的。我选用速度较快的Tarjan求LCA。

    为方便大家(我)理解,我在代码核心部分增添了一些注释。

    C++ Code:

    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<cctype>
    using namespace std;
    #define C c=getchar()
    #define N 300010
    int n,m,ne=0,nq=0,maxdeep=0;
    bool vis[N];
    int f[N],head[N],que[N],w[N],deep[N],mk[N],ans[N],tong[N<<1];
    vector<int>js[N],js2[N],js3[N];//用于差分。由于直接开数组会炸,而实际要用的空间不多,所以用vector 
    struct query{
        int same,nxt,to,num;
        bool flag;
    }q[N<<1];
    struct edge{
        int to,nxt;
    }e[N<<1];
    struct men{
    	int x,y,len,lca;
    }a[N];
    inline int readint(){
        char C;
        for(;!isdigit(c);C);
        int d=0;
        for(;isdigit(c);C)
        d=(d<<3)+(d<<1)+(c^'0');
        return d;
    }
    inline void add_edge(int x,int y){
        e[++ne].to=y;
        e[ne].nxt=head[x];
        head[x]=ne;
        e[++ne].to=x;
        e[ne].nxt=head[y];
        head[y]=ne;
    }
    inline void add_que(int x,int y,int z){
        q[++nq].to=y;
        q[nq].same=nq+1;
        q[nq].num=z;
        q[nq].nxt=que[x];
        que[x]=nq;
        q[++nq].to=x;
        q[nq].same=nq-1;
        q[nq].num=z;
        q[nq].nxt=que[y];
        que[y]=nq;
    }
    int find(int x){
        if(f[x]==x)return x;
        return f[x]=find(f[x]);
    }
    void tarjan(int root){
        for(int i=head[root];i;i=e[i].nxt){
            int v=e[i].to;
            if(deep[v])continue;
            if(maxdeep<(deep[v]=deep[root]+1))maxdeep=deep[v];//求每个点到根节点的深度和树的最大深度 
            tarjan(v);
            f[v]=root;
            vis[v]=true;
        }
        for(int i=que[root];i;i=q[i].nxt)
        if(vis[q[i].to]&&!q[i].flag){
        	int p=q[i].num;
            a[p].len=deep[a[p].x]+deep[a[p].y]-(deep[a[p].lca=find(q[i].to)]<<1);//顺便算出每个人跑的总长度 
            q[i].flag=q[q[i].same].flag=true;
        }
    }//Tarjan求LCA 
    void dfs1(int rt){//deep[s]=deep[i]+w[i]
    	int now=deep[rt]+w[rt],pre;//now记录等式右边,tong统计等式左边。pre记录开始时的tong[now] 
    	if(now<=maxdeep)pre=tong[now];//当now超过最大深度则无需计算 
    	for(int i=head[rt];i;i=e[i].nxt)
    	if(deep[rt]<deep[e[i].to])dfs1(e[i].to);
    	tong[deep[rt]]+=mk[rt];
    	if(now<=maxdeep)ans[rt]+=tong[now]-pre;//当now超过最大深度则无需计算,否则更新答案
    	//由于tong[now]在深搜过程中已经变化,那么变化的值就是可被观测到的人数。 
    	for(int i=js[rt].size()-1;i>=0;--i)--tong[js[rt][i]];//把lca为当前节点的减去 
    }
    void dfs2(int rt){//deep[t]-len=deep[i]-w[i]
    	int now=deep[rt]-w[rt]+300000,pre;//now、tong、pre同上 
    	pre=tong[now];
    	for(int i=head[rt];i;i=e[i].nxt)
    	if(deep[rt]<deep[e[i].to])dfs2(e[i].to);
    	for(int i=js2[rt].size()-1;i>=0;--i)++tong[js2[rt][i]+300000];//加上等式左边的东西 
    	ans[rt]+=tong[now]-pre;//原理同上 
    	for(int i=js3[rt].size()-1;i>=0;--i)--tong[js3[rt][i]+300000];//把无用节点减去 
    }
    int main(){
        n=readint(),m=readint();
        memset(vis,0,sizeof vis);
        memset(deep,0,sizeof deep);
        memset(ans,0,sizeof ans);
        deep[1]=1;
        for(int i=1;i<n;i++){
            int u=readint(),v=readint();
            add_edge(u,v);
        }
        for(int i=1;i<=n;++i)w[f[i]=i]=readint();
        for(int i=1;i<=m;i++){
        	a[i].x=readint();
        	a[i].y=readint();
        	if(a[i].x!=a[i].y)
    		add_que(a[i].x,a[i].y,i);else{
    			a[i].len=0;
    			a[i].lca=a[i].x;
    		}
    	}//保存LCA询问,Tarjan 
        tarjan(1);
        memset(tong,0,sizeof tong);
        memset(mk,0,sizeof mk);//mk[i]表示第i个节点作为起点多少次 
        for(int i=1;i<=m;++i){
        	++mk[a[i].x];
        	js[a[i].lca].push_back(deep[a[i].x]);//js[i]存储lca为i的人的起点的深度 
        }
        dfs1(1);//处理s->lca(s,t)路径 
        memset(tong,0,sizeof tong);
        for(int i=1;i<=m;++i){
        	int f=deep[a[i].y]-a[i].len;
        	js2[a[i].y].push_back(f);//js2[i]存储终点为i的人的终点的深度减去这个人走的总长 
        	js3[a[i].lca].push_back(f);//js3[i]存储lca为i的人的终点的深度减去这个人走的总长
        	//这两个数组用来差分 
        }
        dfs2(1);
        for(int i=1;i<=m;++i)
        if(deep[a[i].x]-deep[a[i].lca]==w[a[i].lca])--ans[a[i].lca];
        //当lca(s,t)正好可以观测到这个人时,它在s->lca(s,t)和lca(s,t)->t都会被统计一次,所以去重。
    	for(int i=1;i<n;++i)printf("%d ",ans[i]);
    	printf("%d
    ",ans[n]);
        return 0;
    }
    

    在BZOJ上PE是什么鬼!?

  • 相关阅读:
    文件参数Python读取wav格式文件
    电子工程术语和定义列表,按字母顺序排列
    MAC地址加减1算法
    uboot通过kernel command line 动态分区 CONFIG_MTD_CMDLINE_PARTS
    c调用shell脚本
    busybox提示can't access tty.job control turned off
    cut命令如何截取以空格隔开的字段
    DS28E01100
    busybox 中的ntpd使用
    Debugfs
  • 原文地址:https://www.cnblogs.com/Mrsrz/p/7688293.html
Copyright © 2020-2023  润新知