其他分享
首页 > 其他分享> > [NOIP2016 提高组] 天天爱跑步

[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