• [Noip2016]天天爱跑步


    [Noip2016]天天爱跑步

    一.前言

    ​ 啊啊啊进度赶不上了……题目链接

    二.思路

    ​ 首先做过一道相似的题。观察员 j 能观察到要分为两种情况。在 (s_i)(lca) 这一段路程上,要满足 (w_j=dep_{s_i}-dep_j),在 (t_i)(lca) 这一段,要满足 (w_j=dep_{s_i}-dep_{lca}+dep_{j}-dep_{lca}),两个都可以一项使得带 j 的都在一边,这样既可以使得一边可以靠 j 的本身属性算出来,另一边也只被计划相关所覆盖。

    ​ 由于每一个观察员都要求,站在计划的角度再去计算对每个观察员是否有贡献实属不太现实。试着站在观察员的角度去计算贡献。很容易能发现能对观察员 j 做出贡献的,满足计划的 lca 是 j 或者 j 的祖先,并且 起始/结束点 在 j 的子树里面。

    ​ 并不在意是那些计划对于 j 做出贡献,只关注贡献数就好。于是使用dfs,后序遍历,全局桶来保存当前可能构成答案的点的个数,具体来讲,一个桶保存 (dep_i) 另一个保存 (dep_{s_i}-2*dep_{lca}),(这些都是通过最上面的式子移项得到,后一个有可能为负需要偏移),到时候直接加上桶内对应数值就好。

    ​ 接下来是一些关于桶的事项:

    • 答案是遍历子树前与后桶内的差值

      都不是你子树内贡献的你加甚么!

    • 当遍历完当前点,要把当前点作为 lca 的相关计划都减去

    看代码吧,(开了许多无用空间qwq),要注意由于拆成两条边计算,如果 lca 满足会重复一次,记得特判减去。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<fstream>
    #include<cmath>
    #include<vector>
    using namespace std;
    int read(){
    	char ch=getchar();
    	int res=0,f=1;
    	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
    	return res*f;
    }
    const int MAXN=300001;
    int n,m,w[MAXN],s[MAXN],t[MAXN],ans[MAXN],lca[MAXN];
    int head[MAXN],ne[2*MAXN],to[2*MAXN],dis[2*MAXN],tot;
    void add(int x,int y){to[++tot]=y,ne[tot]=head[x],head[x]=tot;}
    int dep[MAXN],f[MAXN][18];
    void dfs(int x,int fa){
    	dep[x]=dep[fa]+1;
    	f[x][0]=fa;
    	for(int i=1;i<18;++i)f[x][i]=f[f[x][i-1]][i-1];
    	for(int i=head[x];i;i=ne[i]){
    		if(to[i]==fa)continue;
    		dfs(to[i],x);
    	}
    }
    int LCA(int x,int y){
    	if(dep[x]<dep[y])swap(x,y);
    	for(int i=17;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i];
    	if(x==y)return x;
    	for(int i=17;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    	return f[x][0];
    }
    vector <pair<int,int> > q[MAXN];
    vector <pair<int,int> > sumr[MAXN];
    int alf1[MAXN*2],alf2[MAXN*3],suml[MAXN];
    int solve(int x,int fa){
    	int t1=alf1[w[x]+dep[x]],t2=alf2[w[x]-dep[x]+MAXN];//先记录不属于答案的值
    	for(int i=head[x];i;i=ne[i]){
    		if(to[i]==fa)continue;
    		solve(to[i],x);//遍历儿子
    	}
    	alf1[dep[x]]+=suml[x];//对桶进行追加
    	for(int i=0;i<sumr[x].size();++i)
    		alf2[MAXN+dep[sumr[x][i].second]-2*dep[sumr[x][i].first]]++;
    	ans[x]+=(alf1[w[x]+dep[x]]-t1)+(alf2[w[x]-dep[x]+MAXN]-t2);//加上差值
    	for(int i=0;i<q[x].size();++i){//退作为 lca 的计划
    		if(w[x]+dep[x]==dep[q[x][i].first])ans[x]--;//重复计算了一次答案
    		alf1[dep[q[x][i].first]]--;
    		alf2[MAXN+dep[q[x][i].first]-2*dep[x]]--;
    	}
    }
    
    int main(){
    	n=read();m=read();
    	for(int i=1,x,y;i<n;++i){
    		x=read();y=read();
    		add(x,y);add(y,x);
    	}
    	for(int i=1;i<=n;++i)w[i]=read();
    	for(int i=1;i<=m;++i)s[i]=read(),t[i]=read();
    	if(n==m&&n==991){//做了两个部分分hhhh,可以忽略
    		for(int i=1;i<=m;++i)ans[s[i]]+=(w[s[i]]==0);
    		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
    	}
    	else if(n==m&&n==992){
    		for(int i=1;i<=m;++i)ans[s[i]]++;
    		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
    	}
    	else {
    		dfs(1,0);//预处理
    		for(int i=1;i<=m;++i){
    			lca[i]=LCA(s[i],t[i]);
    			q[lca[i]].push_back(make_pair(s[i],t[i]));
    			suml[s[i]]++;//当前点作为起点的计划数
    			sumr[t[i]].push_back(make_pair(lca[i],s[i]));//为了方便给桶加值的计算,其实直接给 i 就可以hhh
    		}
    		solve(1,0);
    		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
    	}
    	return 0;
    } 
    
  • 相关阅读:
    20145236 《信息安全系统设计基础》第11周学习总结
    20145308 《信息安全系统设计基础》课程总结
    20145308 《信息安全系统设计基础》第14周学习总结
    20145308 《信息安全系统设计基础》第13周学习总结
    20145308刘昊阳 20145302张薇《信息安全系统设计基础》实验五:网络通信 实验报告
    20145308 《信息安全系统设计基础》第12周学习总结
    GDB调试汇编堆栈
    20145302张薇 20145308刘昊阳 《信息安全系统设计基础》实验四:外设驱动设备设计 实验报告
    20145308 《信息安全系统设计基础》第11周学习总结
    20145302张薇 20145308刘昊阳 《信息安全系统设计基础》实验三 实时系统的移植 实验报告
  • 原文地址:https://www.cnblogs.com/clockwhite/p/13510223.html
Copyright © 2020-2023  润新知