Description
Input
第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。
Output
输出1行n个整数,第j个整数表示结点j的观察员可以观察到多少人。
Sample Input
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
Sample Output
2 0 0 1 1 1
Hint
提示:
对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。
对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。
对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。
对于4号点,玩家1被观察到,共1人被观察到。
对于5号点,玩家2被观察到,共1人被观察到。
对于6号点,玩家3被观察到,共1人被观察到。
如果你的程序需要用到较大的栈空间(这通常意味着需要较深层数的递归),请务必仔细阅读选手目录下的文档running/stackpdf,以了解在最终评测时栈空间的限制与在当前工作环境下调整栈空间限制的方法。
Solution
以下部分分算法引自 传送门
测试点1——5:
数组a[i][j]表示点i在j时刻经过的人数,然后一个人一个人的dfs,记录时刻time
如果到了终点,递归回退时a[now][time]++
令watch[i]=j表示i号节点观察员在j时刻出现
输出a[i][watch[i]]
1 void run(int now,int end,int time,int fa)
2 {
3 if(now==end)
4 {
5 ok=true;
6 a[now][time]++;
7 return;
8 }
9 if(ok) return;
10 for(int i=front[now];i;i=nextt[i])
11 {
12 if(to[i]==fa) continue;
13 run(to[i],end,time+1,now);
14 }
15 if(ok) a[now][time]++;
16 }
测试点6——8:
树退化为一条链,而且点的编号就是深度
所以对于链上一个观察员,他只能观察到从两个位置出发的人i+watch[i],i-watch[i]
如果i能观察到起点深度比他小的人,那么这个人的终点要>=i
如果i能观察到起点深度比他大的人,那么这个人的终点要<=i
所以,
用链表存储从每个节点起跑的人,
枚举每个观察员,然后判断两个位置的人是否满足要求
1 void solve2()
2 {
3 for(int i=1;i<=n;i++)
4 {
5 if(i-watch[i]>=1)
6 {
7 for(int j=front[i-watch[i]];j;j=nextt[j])
8 {
9 if(i<=e[to[j]].t) ans[i]++;
10 }
11 }
12 if(i+watch[i]<=n)
13 {
14 for(int j=front[i+watch[i]];j;j=nextt[j])
15 {
16 if(i>=e[to[j]].t) ans[i]++;
17 }
18 }
19 }
20 }
测试点9——12:
所有人的起点都是1,假设1号的深度为0,
那么只有观察员节点的深度等于出现时间,他才能观察到
所以先判断深度是否等于出现时间,不等,直接输出0,下一个
若相等,他一定能观察到经过他的所有人
也就是说,以观察员i为根的子树中有跑步者的终点,观察员i就能观察多少人
所以,终点+1,记录每个节点的深度,我用的bfs,单后dfs统计子树1的个数,
kids[]表示答案
1 void bfs1()
2 {
3 queue<node2>q;
4 cur.d=0 ;cur.who=1; fa[1]=0;
5 q.push(cur);
6 while(!q.empty())
7 {
8 cur=q.front(); q.pop();
9 for(int i=front[cur.who];i;i=nextt[i])
10 {
11 if(to[i]==fa[cur.who]) continue;
12 fa[to[i]]=cur.who;
13 nxt.d=cur.d+1; nxt.who=to[i]; deep[nxt.who]=deep[cur.who]+1;
14 q.push(nxt);
15 }
16 }
17 }
18 void dfs1(int now)
19 {
20 kids[now]=sum[now];
21 for(int i=front[now];i;i=nextt[i])
22 {
23 if(to[i]==fa[now]) continue;
24 dfs1(to[i]);
25 kids[now]+=kids[to[i]];
26 }
27 }
测试点13——16:
所有人的终点都是1
也就是说,第i个观察员只能观察到第deep[i]+watch[i]层的跑步者
所以,若跑步者能被观察员i观察到,要满足以下两个条件:
1、在起点观察员i的子树内
2、起点的层数=deep[i]+watch[i]
先对原树dfs一遍,记录观察员i的子树dfs序范围in[i]——out[i]
对每一层维护一颗线段树,动态开节点
查询时,找到deep[i]+watch[i]这一层的线段树,查in[i]——out[i]有多少个起点
1 void add(int &now,int pos,int l,int r,int cnt)
2 {
3 if(!now) now=++tot2;
4 sum[now]+=cnt;
5 if(l==r) return;
6 int mid=l+r>>1;
7 if(pos<=mid) add(lc[now],pos,l,mid,cnt);
8 else add(rc[now],pos,mid+1,r,cnt);
9 }
10 void dfs(int now,int pre)
11 {
12 in[now]=++tot;
13 add(root[deep[now]],tot,1,n,siz[now]);
14 for(int i=front[now];i;i=nxt[i])
15 {
16 if(to[i]==pre) continue;
17 deep[to[i]]=deep[now]+1;
18 maxn=max(maxn,deep[to[i]]);
19 dfs(to[i],now);
20 }
21 out[now]=tot;
22 }
23 void query(int now,int opl,int opr,int l,int r)
24 {
25 if(!now) return;
26 if(l>=opl&&r<=opr) { ans+=sum[now]; return; }
27 int mid=l+r>>1;
28 if(opl<=mid) query(lc[now],opl,opr,l,mid);
29 if(opr>mid) query(rc[now],opl,opr,mid+1,r);
30 }
-----------------------------------------------引用内容结束-----------------------------------------------------
正解
对于一条路径u->v,我们可以剖成往上和往下的两部分
对于向上的u->lca,如果中间有点i满足dep[i]+w[i]==dep[u],则这条路径会对i的答案产生贡献
对于向下的lca->v,如果中间有点i满足dep[i]-w[i]==dep[v]-dis(u,v),则这条路径会对i的答案产生贡献
那么我们考虑在dfs时维护每一层的起点数,对于向上的时候,对答案产生贡献开始于u点,终止于lca点,对于向下的时候,对答案产生贡献开始于v点,终止于lca点。
那么我们在一开始用三个数组v1(i)记录下路径lca为i的起点深度,v2(i)记录下路径终点为i的终点深度-dis(路径起点,路径终点),v3(i)记录下路径lca为i的终点深度-dis(路径起点,路径终点)
我们就可以计算向上跑时,在dfs每一个点的时候 向该层加入该点的路径数,统计答案,删除以该点为lca的路径数
同理向下跑 向该层加入终点为该点的路径数,统计答案,删除以该点为lca的路径数
Code
#include<bits/stdc++.h> #define RG register int #define rep(i,a,b) for(RG i=a;i<=b;++i) #define add(u,v) e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt,swap(u,v),e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt #define maxn 300100 #define WY 300000 using namespace std; int n,m,cnt; int fa[maxn],son[maxn],sz[maxn],top[maxn],dep[maxn],head[maxn],num[maxn],tot[maxn<<1],ans[maxn],w[maxn]; vector<int> v1[maxn],v2[maxn],v3[maxn]; struct E{ int v,next; }e[maxn<<1]; inline int read() { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x*f; } void dfs1(int u,int pa) { sz[u]=1,fa[u]=pa,dep[u]=dep[pa]+1; for(int i=head[u];i;i=e[i].next) { int v=e[i].v; if(v==pa)continue; dfs1(v,u); sz[u]+=sz[v]; if(sz[son[u]]<sz[v]) son[u]=v; } } void dfs2(int u,int tp) { top[u]=tp; if(!son[u]) return;dfs2(son[u],tp); for(int i=head[u];i;i=e[i].next) if(e[i].v!=fa[u]&&e[i].v!=son[u]) dfs2(e[i].v,e[i].v); } int find(int x,int y) { while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]]; } return dep[x]<dep[y]?x:y; } void go_up(int u) { int len=dep[u]+w[u]; //计算 离 对目前位置可以产生贡献的起点 的距离 ans[u]-=tot[len]; //减去其他子树中的数量 for(int i=head[u];i;i=e[i].next) if(e[i].v!=fa[u]) go_up(e[i].v); tot[dep[u]]+=num[u]; //加入目前点的起点数 ans[u]+=tot[len]; //计算贡献 for(int i=0,lim=v1[u].size();i<lim;i++) --tot[v1[u][i]]; //减去桶中lca为该点的统计 } void go_down(int u) { int len=dep[u]-w[u]+WY; //加上定值防止len<0 ans[u]-=tot[len]; //减去其他子树中的数量 for(int i=head[u];i;i=e[i].next) if(e[i].v!=fa[u]) go_down(e[i].v); for(int i=0,lim=v2[u].size();i<lim;i++) ++tot[v2[u][i]]; //加入范围内(v==now)的点 ans[u]+=tot[len]; for(int i=0,lim=v3[u].size();i<lim;i++) --tot[v3[u][i]]; //减去范围外(lca==now)的点 } int main() { n=read(),m=read(); for(RG i=1,u,v;i<n;i++) u=read(),v=read(),add(u,v); dfs1(1,0);dfs2(1,0); rep(i,1,n) w[i]=read(); rep(i,1,m) { int u=read(),v=read(),lca=find(u,v); ++num[u];int len=dep[u]+dep[v]-(dep[lca]<<1); v1[lca].push_back(dep[u]),v2[v].push_back(dep[v]-len+WY),v3[lca].push_back(dep[v]-len+WY); if(w[lca]+dep[lca]==dep[u]) --ans[lca]; } go_up(1);memset(tot,0,sizeof(tot)); go_down(1); rep(i,1,n) printf("%d ",ans[i]); return 0; }