洛谷 P6626 [省选联考 2020 B 卷] 消息传递
洛谷 P6626 [省选联考 2020 B 卷] 消息传递
Problem
给一棵有\(n\)个节点的树。有\(m\)个询问,每次给出一对\(x,k\)表示查询到点\(x\)的距离为\(k\)的点有多少个。
\(T\)组数据。
\(1\sim3:1\le n,m\le10^3\\\)
\(4\sim6:1\le n,m\le 10^5,k\le 20\\\)
\(7\sim10:1\le n,m\le 10^5\\\)
\(\forall:1\le T\le5,1\le x\le n,0\le k\le n\).
Solution1
暴力不必多说,写个\(dfs/bfs\)就有\(30pts\),时间复杂度是\(O(n^2)\)。
优化
对点分治还不熟练,所以先来学个另类做法。
将询问离线,然后用换根法进行处理。
将根从\(x\)换为\(y\)时,\(y\)的子树中所有节点深度减一,其他点的深度都加一。用桶来记录深度。
先用树链剖分将树变为序列方便区间操作。
每一次换根,我们都需要做两次区间修改,同时还要维护一个桶。所以不能用线段树,但是可以用分块。对每个块开一个桶即可。
当换根到有询问的点时,暴力拿出对应块的桶进行计数即可。
时间复杂度\(O(Tn\sqrt n)\),空间复杂度\(O(n\sqrt n)\)。
理论可以通过,实际需要开\(O2\).
Code1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<limits.h>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#define debug printf("Now is %d\n",__LINE__);
using namespace std;
template<class T>inline void read(T& x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0'; ch=getchar();
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0'; ch=getchar();
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do { G[++g]=x%10; x/=10; } while(x);
for(int i=g; i>=1; --i)putchar('0'+G[i]); putchar('\n');
}
int T;
#define N 100010
#define M 400
//graph
int head[N],ver[N<<1],nxt[N<<1],cnt;
IL void insert(int x,int y)
{
nxt[++cnt]=head[x];
head[x]=cnt;
ver[cnt]=y;
nxt[++cnt]=head[y];
head[y]=cnt;
ver[cnt]=x;
}
//ask
struct node
{
int k,op,nxt;
}a[N];
int top;
int hh[N];
IL void insert(int x,int k,int op)
{
a[++top].k=k;
a[top].op=op;
a[top].nxt=hh[x];
hh[x]=top;
}
//pre
int dis[N],sze[N],dfn[N],dfncnt,m,n;
int b[N];
void dfs(int x,int f)
{
dis[x]=dis[f]+1;
sze[x]=1;
dfn[x]=++dfncnt;
b[dfncnt]=dis[x];
for(int i=head[x];i;i=nxt[i])
{
if(ver[i]==f) continue;
dfs(ver[i],x);
sze[x]+=sze[ver[i]];
}
}
//block
int len,s;
int L[M],R[M],belong[N];
int lazy[M],c[M][N];
void change(int x,int y,int v)
{
re int xx=belong[x],yy=belong[y],i,j;
if(xx==yy)
{
for(i=L[xx];i<=R[xx];i++)
{
c[xx][b[i]]--;
b[i]+=lazy[xx];
}
lazy[xx]=0;
for(i=x;i<=y;i++)
{
b[i]+=v;
}
for(i=L[xx];i<=R[xx];i++)
{
c[xx][b[i]]++;
}
}
else
{
for(i=xx+1;i<=yy-1;i++)
{
lazy[i]+=v;
}
for(i=L[xx];i<=R[xx];i++)
{
c[xx][b[i]]--;
b[i]+=lazy[xx];
}
for(i=L[yy];i<=R[yy];i++)
{
c[yy][b[i]]--;
b[i]+=lazy[yy];
}
lazy[xx]=lazy[yy]=0;
for(i=x;i<=R[xx];i++)
{
b[i]+=v;
}
for(i=L[yy];i<=y;i++)
{
b[i]+=v;
}
for(i=L[xx];i<=R[xx];i++)
{
c[xx][b[i]]++;
}
for(i=L[yy];i<=R[yy];i++)
{
c[yy][b[i]]++;
}
}
}
int ans[N];
void dfs1(int x,int f)
{
re int i,j;
for(i=hh[x];i;i=a[i].nxt)
{
for(j=1;j<=s;j++)
{
if(a[i].k+1-lazy[j]>=0) ans[a[i].op]+=c[j][a[i].k+1-lazy[j]];
}
}
for(i=head[x];i;i=nxt[i])
{
if(ver[i]==f) continue;
for(j=1;j<=s;j++) lazy[j]++;
change(dfn[ver[i]],dfn[ver[i]]+sze[ver[i]]-1,-2);
dfs1(ver[i],x);
for(j=1;j<=s;j++) lazy[j]--;
change(dfn[ver[i]],dfn[ver[i]]+sze[ver[i]]-1,2);
}
}
int main()
{
T=read();
int i,j,x,k;
while(T--)
{
//init
memset(head,0,sizeof(head));cnt=0;
memset(hh,0,sizeof(hh));top=0;
dfncnt=0;
memset(lazy,0,sizeof(lazy));
memset(ans,0,sizeof(ans));
//work
n=read();
m=read();
for(i=1;i<n;i++) insert(read(),read());
dfs(1,0);
for(i=1;i<=m;i++)
{
x=read();
k=read();
insert(x,k,i);
}
len=sqrt(n);
s=n/len;
for(i=1;i<=s;i++)
{
L[i]=n/s*(i-1)+1;
R[i]=n/s*i;
}
R[s]=n;
for(i=1;i<=s;i++)
{
for(int j=L[i];j<=R[i];j++)
{
belong[j]=i;
}
}
for(i=1;i<=s;i++)
{
lazy[i]=0;
for(int j=L[i];j<=R[i];j++)
{
c[i][b[j]]++;
}
}
dfs1(1,0);
for(i=1;i<=m;i++) cout<<ans[i]<<endl;
for(i=1;i<=s;i++)
{
lazy[i]=0;
for(j=L[i];j<=R[i];j++)
{
c[i][b[j]]=0;
}
}
}
return 0;
}
Solution2
再看题目像点分治,但是这里指定了目标点。
猜想把询问离线,那就考虑怎么分别计算贡献。