题目
难道还有谁不知道的么
个人恩仇录
这是我第一次正经参加noip,然后遇到了这道题,记得当时好像连树都不会,还是很执着的搞掉了前4个点,然后还很用心的做了链那一个点并且自信过了七个点,现在回想一下当年的自信是哪里来的,原来是不会分析时间复杂度。。。
不过还是过了4个点,最后听说这是16年最难的题目就很开心啊,还有好多爆0的,我们省只有一个满分。
所以后来对这道题心存畏惧,然后第一次接触LCA和差分的时候还自信慢慢的来重新看了看题,结果还是不会。。。
最后是接近NOIP2017了,差分考得又比较多,所以感觉有必要研究一下,看了一个晚上的题解写了两个晚上才写出来,其实想一想这道题挺简单的,就是有细节上的失误和对这道题的畏惧之情。
反正最后搞出来了还是特别开心嘛。
分析
反正这是一个差分嘛,因为如果模拟的话时间复杂度是O(n*m*log2n),那就只有往差分的思路上想了。
注意:这里的差分指的尽量对于一个路径只标记几个点
首先抓住题目的一个要点——一个点之后固定起点的几个位置才有可能影响到这个点,如果强行统计的话时间复杂度还是太高,并且不知道时候影响到了结点(可能没走到就完了),还是要差分的。
我们可以很自然的或者说看看样例的把它拆成两条路,一个是上行,一个是下行。
我们来推一下满足什么要求才是影响结点的起点位置:
上行:dep[i]+w[i]==dep[u](很自然的移项,只和i有关)
下行:dep[i]-w[i]==2*dep[z]-dep[u] (z是路径两点的LCA)
所以我们在u标记上行,z删除,v标记下行,fa[z]删除(避免重复计算)
每次答案的统计就是子树中满足dep[i]+-w[i]的数量。
因为dep[i]+-w[i]都比较小,所以可以设置两个桶,为当前的某个值的数量
因为是统计子树,所以要设置一个变量为刚刚到达结点i的时候对应标记的值,最后来减去才是在子树中增加的值。
说明:为什么要分成两段路径?
因为常规的差分并没有时间这个要求,所以我们大概可以看成从两边同时往LCA走,但是这里要求了时间,如果拆成两段路径的话比较符合题意,这也是真实的走法。
code
//我就是不用vector
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int oo=18,maxn=300000+10;
#define cntb(i) cntb[i+maxn]
#define cnta(i) cnta[i]
void Rd(int &x)
{
x=0;
char c;
while((c=getchar())!=EOF && c<'0');
do{
x=(x<<3)+(x<<1)+(c^48);
}while((c=getchar())!=EOF && c>='0');
}
int np,first[maxn];
struct edge{
int to,next;
}E[maxn<<1];
void add(int u,int v)
{
E[++np]=(edge){v,first[u]};
first[u]=np;
}
int n,m,w[maxn];
int dfn[maxn],dep[maxn],fa[maxn][20];
int dfs_clock;
int cnta[maxn<<1],cntb[maxn<<1];
int ans[maxn];
struct data{
int m,id;
friend bool operator<(data a,data b)
{
return dfn[a.id]<dfn[b.id];
}
}B[maxn],B0[maxn],A0[maxn],A[maxn];
int subb=1,subb0=1,suba0=1,suba=1;
void Init()
{
int u,v;
Rd(n);Rd(m);
for(int i=1;i<n;i++)
{
Rd(u);
Rd(v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
Rd(w[i]);
dfn[0]=maxn+5;
}
void DFS(int i,int f,int d)
{
dfn[i]=++dfs_clock;
dep[i]=d;
fa[i][0]=f;
for(int j=1;j<=oo;j++)
fa[i][j]=fa[fa[i][j-1]][j-1];
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
if(j==f)continue;
DFS(j,i,d+1);
}
}
int LCA(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
int delt=dep[u]-dep[v];
for(int j=0;j<=oo;j++)
if(delt&(1<<j))
u=fa[u][j];
if(u==v)return u;
for(int j=oo;j>=0;j--)
if(fa[u][j]!=fa[v][j])
u=fa[u][j],v=fa[v][j];
return fa[u][0];
}
void update()
{
int u,v,z;
for(int i=1;i<=m;i++)
{
Rd(u);
Rd(v);
z=LCA(u,v);
A[i]=(data){dep[u],u};
A0[i]=(data){dep[u],fa[z][0]};
B[i]=(data){2*dep[z]-dep[u],v};
B0[i]=(data){2*dep[z]-dep[u],z};
}
sort(A+1,A+m+1);
sort(B+1,B+m+1);
sort(B0+1,B0+m+1);
sort(A0+1,A0+m+1);
}
void DFS(int i,int f)
{
int now1=cnta(dep[i]+w[i]);
int now2=cntb(dep[i]-w[i]);
while(suba<=m&&A[suba].id==i)cnta(A[suba++].m)++;
while(subb<=m&&B[subb].id==i)cntb(B[subb++].m)++;
while(suba0<=m&&A0[suba0].id==i)cnta(A0[suba0++].m)--;
while(subb0<=m&&B0[subb0].id==i)cntb(B0[subb0++].m)--;
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
if(j==f)continue;
DFS(j,i);
}
ans[i]= cnta(dep[i]+w[i]) + cntb(dep[i]-w[i]) - now1 - now2;
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out1.t","w",stdout);
Init();
DFS(1,0,0);
update();
DFS(1,0);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}
注意
1、dep[i]-w[i]要加偏移变量
2、为了避免本校题库对STL的抗拒,特地想了一个办法来避免使用vector:使用一个数组记录下要修改的值,按照便利顺序排序(写一个dfs_clock),但是要注意的问题就是dfn[0]要赋值为inf,或者在弹出元素的时候写一个判断把0弹出来,反正确实要小心