NOIP2016 天天爱跑步
转载请注明出处:
http://www.cnblogs.com/hzoi-wangxh/p/7738626.html
天天爱跑步
时间限制: 2 Sec 内存限制: 512 MB题目描述
小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到n的连续正整数。
现在有m个玩家,第i个玩家的起点为Si,终点为Ti。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)
小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道每个观察员会观察到多少人?
注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。
输入
第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。
输出
样例输入
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
样例输出
2 0 0 1 1 1
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; struct tree{ int u,v,next; }l[601000]; struct tree2{ int st,en,go; }ll[301000]; int n,lian[301000],e,cnt,m,dep[301000],ldfn[301000],rdfn[301000]; int lc[7501000],rc[7501000],w[301000],p[300100][22],root[7501000]; int son[301000],size[301000],hh[7501000],fa[301000],an[300100],num; void bian(int,int); void dfs(int); void work(); int lca(int,int); void change(int,int,int,int,int&); int search(int,int,int,int,int); int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); bian(x,y); bian(y,x); } for(int i=1;i<=n;i++) scanf("%d",&w[i]); dfs(1); work(); for(int i=1;i<=m;i++) { scanf("%d%d",&ll[i].st,&ll[i].en); ll[i].go=lca(ll[i].st,ll[i].en); } for(int i=1;i<=m;i++) { change(ldfn[ll[i].st],1,1,n,root[dep[ll[i].st]]); change(ldfn[fa[ll[i].go]],-1,1,n,root[dep[ll[i].st]]); } for(int i=1;i<=n;i++) an[i]+=search(ldfn[i],rdfn[i],1,n,root[dep[i]+w[i]]); memset(lc,0,sizeof(lc)); memset(rc,0,sizeof(rc)); memset(root,0,sizeof(root)); cnt=0; memset(hh,0,sizeof(hh)); for(int i=1;i<=m;i++) { change(ldfn[ll[i].en],1,1,n,root[dep[ll[i].st]-2*dep[ll[i].go]+2*n]); change(ldfn[ll[i].go],-1,1,n,root[dep[ll[i].st]-2*dep[ll[i].go]+2*n]); } for(int i=1;i<=n;i++) an[i]+=search(ldfn[i],rdfn[i],1,n,root[w[i]-dep[i]+2*n]); for(int i=1;i<=n;i++) printf("%d ",an[i]); return 0; } void bian(int x,int y) { e++; l[e].u=x; l[e].v=y; l[e].next=lian[x]; lian[x]=e; } void dfs(int x) { ldfn[x]=++num; size[x]=1; for(int i=lian[x];i;i=l[i].next) { int v=l[i].v; if(v!=fa[x]) { fa[v]=x; dep[v]=dep[x]+1; dfs(v); size[x]+=size[v]; if(size[v]>size[son[x]]) son[x]=v; } } rdfn[x]=num; } void work() { for(int i=1;i<=n;i++) p[i][0]=fa[i]; for(int i=1;i<=20;i++) for(int j=1;j<=n;j++) if(p[j][i-1]!=0) p[j][i]=p[p[j][i-1]][i-1]; } int lca(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int k=dep[x]-dep[y]; for(int i=20;i>=0;i--) if(k-(1<<i)>=0) { k-=(1<<i); x=p[x][i]; } if(x==y) return x; for(int i=20;i>=0;i--) if(p[x][i]!=0&&p[x][i]!=p[y][i]) { x=p[x][i]; y=p[y][i]; } return fa[x]; } void change(int x,int num,int le,int ri,int &now) { if(x==0) return; if(now==0) now=++cnt; hh[now]+=num; if(le==ri) return; int mid=(le+ri)>>1; if(x<=mid) change(x,num,le,mid,lc[now]); else change(x,num,mid+1,ri,rc[now]); } int search(int ll,int rr,int le,int ri,int now) { if(now==0) return 0; if(ll==le&&rr==ri) return hh[now]; int mid=(le+ri)>>1; if(rr<=mid) return search(ll,rr,le,mid,lc[now]); else if(ll>mid) return search(ll,rr,mid+1,ri,rc[now]); else return search(ll,mid,le,mid,lc[now])+search(mid+1,rr,mid+1,ri,rc[now]); }动态开点线段树可以有很多打法,不一定像我一样打。