[Noip2016]天天爱跑步

题目描述

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的起点为Si ,终点为Ti 。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道每个观察员会观察到多少人?注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

输入格式

第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。

接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。

接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。

接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。

对于所有的数据,保证 1<=Si,Ti<=N,0<=Wj<=N

输出格式

输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。

img


这题暴力分很多,所以我们可以一点点地拿。

Si=Ti——送分的数据,直接统计每个w为0的点有多少人以它为起点即可。

w=0——只需要判断有多少个人以当前点为起点即可。

n,m≤1000——暴力往上走,跟着统计有多少点能观测到即可。


上面都是送分的部分,足足给了25分。

退化成链的部分——无非就是向左走和向右走,两者类似。设当前点为u,dep(x)表示x与链首的距离,如果要对u产生贡献,那么需要满足:

\[dep[u]+w[u]=dep[s]——往左走\\ dep[u]-w[u]=dep[s]——往右走 \]

然后记录下有哪些人以当前点为起点和哪些人以当前点为终点,开个桶来计数。

设cntl(x)表示在所有往左走的情况中x出现的次数,cntr(x)表示在所有往右走的情况中x出现的次数,sl(u)表示往左走的起点为u的路径集合,sr(u)表示往右走的起点为u的路径的集合,s(x)表示第x条路的起点。那么若当前点为u,则记录下当前的cntl(dep(u)+w(u))和cntr(dep(u)-w(u)),然后往链的末尾递归其它点,把cntl(dep(s(sl(u))))++,cntr(dep(s(sr(u))))++,等回溯时,答案就是现在的cntl(dep(s(sl(u))))减去之前的cntl(dep(s(sl(u))))再加上现在的cntr(dep(s(sr(u))))减去之前的cntr(dep(s(sr(u))))。

Si=1——设当前点为u,dep(x)表示x的深度,如果要对u产生贡献,那么需要满足:

\[w[u]=dep[u]{\Rightarrow}dep[u]-w[u]=0=dep[s] \]

类似于链的部分,设cnt(x)表示x出现的次数。那么若当前点为u,则记录下当前的cnt(dep(u)-w(u)),即cnt(0)。然后递归子树,把cnt(dep(s))++,等回溯时,答案就是现在的cnt(0)减去之前的cnt(0)。

Ti=1——设当前点为u,dep(x)表示x的深度,如果要对u产生贡献,那么需要满足:

\[dep[u]+w[u]=dep[s] \]

类似于上面的做法。


你现在已经有80分了。而且离正解也很近了

拓展到一般情况就成了以下的几步操作:

1.对于每条路径,求出两个端点u,v的lca,把一条路分成u->lca和lca->v两部分。

2.设su(x)表示s在t上方的路径中以x为起点的路径的集合,sd(x)表示s在t下方的路径中以x为起点的路径的集合,tu(x)和td(x)类似。处理出它们

3.对于当前点u,记录下当前的cnt(dep(u)+w(u))和cnt(dep(u)-w(u)),可以列出对答案做贡献的算式:

\[dep[u]+w[u]=dep[s[sd[u]]]\\ dep[u]-w[u]=dep[t[td[u]]]-dist(s[td[u]],t[td[u]])-pretime(td[u]) \]

其中pretime(x)表示x这条路的起点是不是原来路径的lca,如果是,那么pretime(x)表示到达lca的时刻,否则为0。那么我们每次把计数的数组加1即可。最后的答案类似于之前的方式计算即可。

4.最后由于当前的子树已经处理完,我们需要把tu(u)和su(u)的地方撤销。

需要注意的地方:

为了防止计数的数组超界(减成负数),建议把它开大两倍并且加上一个较大的值。

关于路径的数组也开大两倍,因为我们会从lca的地方把路径断成两段。

由于若当前点为lca,那么答案要剪1,因为被计算了两次

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define maxn 300001
#define maxm 300001
using namespace std;

struct graph{
    struct edge{
        int to,next; bool flag;
        edge(){}
        edge(const int &_to,const int &_next,const bool &_flag){ to=_to,next=_next,flag=_flag; }
    }e[maxn<<1];
    int head[maxn],k;
    inline void init(){ memset(head,-1,sizeof head); }
    inline void add(const int &u,const int &v,const bool &f){ e[k]=edge(v,head[u],f),head[u]=k++; }
}g,q;//g为题目给的树,q为询问

vector<int> su[maxn],sd[maxn],tu[maxn],td[maxn];
int s[maxm<<1],t[maxm<<1],tot;
int pretime[maxm<<1],dis[maxm<<1],cnts[maxn<<1],cntt[maxn<<1],Lca[maxm<<1];
int fa[maxn],dep[maxn];
bool vis[maxn];
int n,m,w[maxn],ans[maxn];

inline int read(){
    register int x(0),f(1); register char c(getchar());
    while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
    while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}

/*----------------tarjan---------------*/
int get(int x){ return x==fa[x]?x:fa[x]=get(fa[x]); }
void lca_tarjan(int u,int pre){
    fa[u]=u,dep[u]=dep[pre]+1,vis[u]=true;
    for(register int i=g.head[u];~i;i=g.e[i].next){
        int v=g.e[i].to;
        if(v!=pre) lca_tarjan(v,u),fa[v]=u;
    }
    for(register int i=q.head[u];~i;i=q.e[i].next){
        int v=q.e[i].to; bool rev=false;
        if(!vis[v]||q.e[i].flag) continue;
        q.e[i].flag=q.e[i^1].flag=true;
        int lca=get(v);

        if(i&1) rev=true,swap(u,v);
        if(u==lca){
            s[++tot]=u,t[tot]=v;
            su[u].push_back(tot),td[v].push_back(tot);
            dis[tot]=dep[v]-dep[u];
        }else if(v==lca){
            s[++tot]=u,t[tot]=v;
            sd[u].push_back(tot),tu[v].push_back(tot);
            dis[tot]=dep[u]-dep[v];
        }else{
            s[++tot]=lca,t[tot]=v;
            su[lca].push_back(tot),td[v].push_back(tot);
            dis[tot]=dep[v]-dep[lca];
            pretime[tot]=dep[u]-dep[lca];
            Lca[++tot]=lca;
            s[tot]=u,t[tot]=lca;
            sd[u].push_back(tot),tu[lca].push_back(tot);
            dis[tot]=dep[u]-dep[lca];
        }
        if(rev) u=v;
    }
}

/*---------------getans---------------*/
void dfs(int u,int pre){
    int nows=cnts[dep[u]+w[u]+maxn],nowt=cntt[dep[u]-w[u]+maxn];
    for(register int i=0;i<sd[u].size();i++) cnts[dep[s[sd[u][i]]]+maxn]++;
    for(register int i=0;i<td[u].size();i++) cntt[dep[t[td[u][i]]]-dis[td[u][i]]-pretime[td[u][i]]+maxn]++;

    for(register int i=g.head[u];~i;i=g.e[i].next){
        int v=g.e[i].to;
        if(v!=pre) dfs(v,u);
    }
    ans[u]=cnts[dep[u]+w[u]+maxn]-nows+cntt[dep[u]-w[u]+maxn]-nowt;
    for(register int i=0;i<tu[u].size();i++) if(Lca[tu[u][i]]==u&&dep[s[tu[u][i]]]+maxn==dep[u]+w[u]+maxn) ans[u]--;

    for(register int i=0;i<tu[u].size();i++) cnts[dep[s[tu[u][i]]]+maxn]--;
    for(register int i=0;i<su[u].size();i++) cntt[dep[t[su[u][i]]]-dis[su[u][i]]-pretime[su[u][i]]+maxn]--;
}

int main(){
/*------------init---------------*/
    g.init(),q.init();
    n=read(),m=read();
    for(register int i=1;i<n;i++){
        int u=read(),v=read();
        g.add(u,v,false),g.add(v,u,false);
    }
    for(register int i=1;i<=n;i++) w[i]=read();
    for(register int i=1;i<=m;i++){
        int u=read(),v=read();
        q.add(u,v,false),q.add(v,u,false);
    }
/*-------------------------------*/
    lca_tarjan(1,0);
    dfs(1,0);
    for(register int i=1;i<=n;i++) printf("%d ",ans[i]);puts("");
    return 0;
}
posted @ 2019-05-27 21:30  修电缆的建筑工  阅读(174)  评论(0编辑  收藏  举报