Noip 2016 天天爱跑步 树上倍增+深搜
Posted Captain_Von
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Noip 2016 天天爱跑步 树上倍增+深搜相关的知识,希望对你有一定的参考价值。
题目:
小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一一棵包含 个结点和 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从到的连续正整数。
现在有个玩家,第个玩家的起点为 ,终点为 。每天打卡任务开始时,所有玩家在第秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)
小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第秒也理到达了结点 。 小C想知道每个观察员会观察到多少人?
注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点作为终点的玩家: 若他在第秒重到达终点,则在结点的观察员不能观察到该玩家;若他正好在第秒到达终点,则在结点的观察员可以观察到这个玩家。
分析:
此题题意明确,要求也不多,很容易想到许许多多暴力方法,也都能得到部分分。但如果想要AC,就必须使用一下几个突破口:
1、求两点的路径时,很容易想到lca,即两点互通时必经之地,方程(一定要用倍增):
lca=Lca(a,b); len=dep[a]+dep[b]-2*dep[lca];//拆成两条路径
2、求两点间路径上的合法点时,用num[0][x]记录路径经过当前结点,且节点深度为x的合法起点节点数目;用num[1][x]记录路径经过当前结点,且节点深度为x的合法终点节点数目;如果我们要求经过当前结点的路径上的合法节点的数目,显然不能直接用num[0][x]加上num[1][x],因为会有一些相同深度的节点不属于同一棵子树,会加多。那么,我们采取差量法,先记录num[0][x]+num[1][x],经过一番dfs子树后,再看num[0][x]+num[1][x]的变量,变化量就是子树的合法节点数量。(但注意一定要减去lca的num,因为在每一次找到lca后都提前加了一次,会算重)
下面是参考代码;
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int lim=100000000; const int maxn=600010; struct point { int to; int nxt; }edge[maxn*2]; int n,Q,tot=0; int dep[maxn]={0},cnt[2]={0},num[2][maxn*2],sum[maxn],f[20][maxn]; bool vis[maxn]; int w[maxn],vnxt[3][maxn*2],vlas[3][maxn*2],vfir[3][maxn*2],vv[3][maxn*2],head[maxn]; void add(int u,int v) { tot++; edge[tot].to=v; edge[tot].nxt=head[u]; head[u]=tot; } void v_add(int x,int s,int v) { int k=++cnt[s]; vv[s][k]=v; vnxt[s][k]=0; if(vfir[s][x]) vnxt[s][vlas[s][x]]=k; else vfir[s][x]=k; vlas[s][x]=k; } void work(int x) { int s1=num[0][dep[x]+w[x]+maxn],s2=num[1][w[x]-dep[x]+maxn]; for(int j=0;j<2;j++) for(int i=vfir[j][x];i;i=vnxt[j][i]) { int dd=vv[j][i]; if(dd<=lim) num[j][dd]++; } for(int i=head[x];i;i=edge[i].nxt) { int v=edge[i].to; if(vis[v]) continue; vis[v]=1; work(v); } for(int j=0;j<2;j++) for(int i=vfir[j][x];i;i=vnxt[j][i]) { int dd=vv[j][i]; if(dd>lim) num[j][dd-lim]--; } sum[x]+=num[0][dep[x]+w[x]+maxn]-s1; sum[x]+=num[1][w[x]-dep[x]+maxn]-s2; } int Lca(int x,int y) { int temp,co=0,lca; int tx=x,ty=y; if(dep[tx]>dep[ty]) swap(tx,ty); temp=dep[ty]-dep[tx]; while(temp) { if(temp&1) ty=f[co][ty]; temp>>=1; co++; } if(tx==ty) return tx; else { for(int j=19;j+1;j--) { if((1<<j)>dep[tx]) continue; if(f[j][tx]!=f[j][ty]) { tx=f[j][tx]; ty=f[j][ty]; } } lca=f[0][tx]; } return lca; } int main() { memset(head,0,sizeof(head)); cin>>n>>Q; for(int i=1;i<=n-1;i++) { int a,b; cin>>a>>b; add(a,b); add(b,a); } for(int i=1;i<=n;i++) cin>>w[i]; /*input over*/ queue<int> q; vis[1]=1; dep[1]=0; q.push(1); while(!q.empty()) { int tt=q.front(); q.pop(); for(int i=head[tt];i;i=edge[i].nxt) { int v=edge[i].to; if(!vis[v]) { vis[v]=1; q.push(v); dep[v]=dep[tt]+1; f[0][v]=tt; } } } for(int j=1;j<20;j++) for(int i=1;i<=n;i++) if(dep[i]>=(1<<j)) f[j][i]=f[j-1][f[j-1][i]]; while(Q--) { int a,b,lca,len; cin>>a>>b; if(a==b) { if(w[a]==0) sum[a]++; continue; } lca=Lca(a,b); len=dep[a]+dep[b]-2*dep[lca]; if(dep[a]-dep[lca]==w[lca]) sum[lca]++; if(a!=lca) { v_add(a,0,maxn+dep[a]); v_add(lca,0,maxn+dep[a]+lim); } if(b!=lca) { v_add(b,1,maxn+len-dep[b]); v_add(lca,1,maxn+len-dep[b]+lim); } } memset(vis,0,sizeof(vis)); vis[1]=1; work(1); for(int i=1;i<=n;i++) cout<<sum[i]<<" "; return 0; }
以上是关于Noip 2016 天天爱跑步 树上倍增+深搜的主要内容,如果未能解决你的问题,请参考以下文章