[NOIP2016 提高组] 天天爱跑步
作者:互联网
题面,题解就不打了,写的较好的题解。
主要是借这个题说一下如何正确的思考出来一道题:
首先样例肯定要模,在模的时候就是构建思路的过程,当然一般的小样例无论什么方法都可以得出答案(spj),能过正确的理解题面就行;
然后我们就可以顺着方案A想,看看是否有漏洞(反例)、复杂度(以这个题来说:每个人(跑m次)一遍一遍统计很好想出来,但稍微一分析就知道并不可行,但此时我们还不能轻易放弃它,想想怎么优化,深挖它的本质)、实现难度(得分效果);
如果不可行——转化思路,并不是换另一种等效做法!既然一个一个统计不行,就从整体着手;
如果可行(无论哪个方案):对于图的问题最好在纸上多动笔画画,推出一点眉目来就想想有没有不全or重复的情况;
分析的差不多了在考虑实现(多注意注意细节:如果不是一模一样就别先复制再改,容易少改某个地方;long long),最好要一气呵成。
既然是题解怎么能没有代码呢?
#include<bits/stdc++.h>
#define Bessie moo~~
// #define int long long
using namespace std;
int read(){
int A=0,FL=1;
char C=getchar();
while(C<'0'||C>'9')FL = C == '-' ? -1:1,C=getchar();
while(C>='0'&&C<='9')A=(A<<3)+(A*2)+(C^'0'),C=getchar();
return A*FL;
}
const int N=300010;
int n,m;
int w[N];
int h[N],etot;
struct edge{
int to,nxt;
}e[N*2],e1[N*2],e2[N*2];
void addedge(int u,int v){
e[++etot].to=v;
e[etot].nxt=h[u];
h[u]=etot;
}
int dep[N],fa[N][20];
void dfs1(int x){
for(int i=1;(1<<i)<=dep[x];i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=h[x],v;i;i=e[i].nxt){
v=e[i].to;
if(v==fa[x][0])continue;
fa[v][0]=x;
dep[v]=dep[x]+1;
dfs1(v);
}
}
int LCA(int x,int y){
if(x==y)return x;
if(dep[x]<dep[y])swap(x,y);
int t=log(dep[x]-dep[y])/log(2);//向上跳的最少次数能达到相同深度
for(int i=t;i>=0;i--){
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
}
t=log(dep[x])/log(2);
for(int i=t;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int b1[N*2],b2[N*2];//两组桶,分别用于上行阶段和下行阶段的贡献统计
int js[N];//统计以每个结点作为起点的路径条数
int dist[N],s[N],t[N];//记录每条跑道的信息
int ans[N];
int h1[N],etot1;
void addedge1(int u,int v){//每个结点作为 终 点 对应的路径集合
e1[++etot1].to=v;
e1[etot1].nxt=h1[u];
h1[u]=etot1;
}
int h2[N],etot2;
void addedge2(int u,int v){//每个结点作为 L C A 对应的路径集合
e2[++etot2].to=v;
e2[etot2].nxt=h2[u];
h2[u]=etot2;
}
// int vis[N];
void dfs2(int x){//核心:从整体地角度出发,不要一个一个统计每一个玩家,要批量处理观察员
// printf("x = %d\n",x);
// vis[x]=1;
int t1=b1[w[x]+dep[x]];// 上行
int t2=b2[w[x]-dep[x]+N];// 下行 加N是为了防止下溢
//因为可能产生在非子树也贡献了答案的情况,解决方案为 计 算 差 值
for(int i=h[x],v;i;i=e[i].nxt){
v=e[i].to;
if(v==fa[x][0])continue;
// printf(" v = %d fa[%d] = %d\n",v,x,fa[x][0]);
dfs2(v);
}
b1[dep[x]]+=js[x];//上行过程中,当前节点作为路径起点,贡献入桶
for(int i=h1[x],v;i;i=e1[i].nxt){//下行过程中,当前节点作为路径起点,贡献入桶
v=e1[i].to;
b2[dist[v]-dep[t[v]]+N]++;
}
ans[x]+=(b1[w[x]+dep[x]]-t1)+(b2[w[x]-dep[x]+N]-t2);
//注意:此时还没结束,考虑到回溯后以此节点为 lca 的起点和终点在桶内产生的贡献已经无效,需要清除
for(int i=h2[x],v;i;i=e2[i].nxt){
v=e2[i].to;
b1[dep[s[v]]]--;
b2[dist[v]-dep[t[v]]+N]--;
}
}
int main(){
n=read(),m=read();
for(int i=1,u,v;i<=n-1;i++){
u=read(),v=read();
addedge(u,v);
addedge(v,u);
}
dep[1]=1;
fa[1][0]=1;
dfs1(1);
for(int i=1;i<=n;i++){
w[i]=read();
}
// for(int i=1;i<=n;i++){
// printf("dep[%d] = %d\n",i,dep[i]);
// }
for(int i=1;i<=m;i++){
s[i]=read(),t[i]=read();
int lca=LCA(s[i],t[i]);
dist[i]=dep[s[i]]+dep[t[i]]-2*dep[lca];
js[s[i]]++;
addedge1(t[i],i);
addedge2(lca,i);
if(dep[lca]+w[lca]==dep[s[i]]){//如果起始或终点就为lca,会多统计一次
ans[lca]--;
}
// printf("lca = %d\n",lca);
}
// for(int i=1;i<=n;i++)printf("%d ",ans[i]);
dfs2(1);
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}
标签:NOIP2016,nxt,fa,int,天天,dep,跑步,b1,b2 来源: https://www.cnblogs.com/Creator-157/p/16409972.html