[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 个整数,第个整数表示结点的观察员可以观察到多少人。
这题暴力分很多,所以我们可以一点点地拿。
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;
}