BZOJ 3626 LCA
Description
给出一个\(n\)个节点的有根树(编号为\(0\)到n-1,根节点为\(0\))。一个点的深度定义为这个节点到根的距离\(+1\)。
设\(dep_{i}\)表示点\(i\)的深度,\(LCA(i,j)\)表示\(i\)与\(j\)的最近公共祖先。
有\(q\)次询问,每次询问给出\(l,r,z\),求\(\sum_{i=l}^{r}dep_{LCA(i,z)}\)。(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)
Input
第一行2个整数\(n,q\)。
接下来\(n-1\)行,分别表示点\(1\)到点\(n-1\)的父节点编号。
接下来\(q\)行,每行\(3\)个整数\(l,r,z\)。
Output
输出\(q\)行,每行表示一个询问的答案。每个答案对\(201314\)取模输出。
Sample Input
5 2
0
0
1
1
1 4 3
1 4 2
Sample Output
8
5
HINT
共\(5\)组数据,\(n\)与\(q\)的规模分别为\(10000,20000,30000,40000,50000\)。
这题被czh随便秒。做法其实很简单,对于\(dep_{LCA(a,b)}\),我们把\(a\)到根的路径上的点的点权\(+1\),询问时我们就可以直接查询\(b\)到根路径上的点权和即可。所以这题就可以做了。
问题在于询问,好像可以建主席树(可能是我口胡的),但离线算法可能更好写。
首先我们知道询问满足可并性$$ans_{l,r,z} = ans_{0,r,z}-ans_{0,l-1,z}$$
所以我们可以将一个询问拆成两个,排序后按顺序修改,分开计算答案即可。(用\(l,r\)修改,\(z\)来查询)。
树链剖分和lct都可以写。
#include<algorithm>
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef long long ll;
#define rhl (201314)
#define maxn (100010)
int N,Q,side[maxn],toit[maxn],next[maxn],tot,id[maxn],tree[maxn*4],inc[maxn*4];
int size[maxn],heavy[maxn],cnt,father[maxn],top[maxn],ans[maxn],key[maxn*4];
struct SCAN
{
int pos,key,ord,sign;
friend inline bool operator <(const SCAN &a,const SCAN &b) { return a.pos < b.pos; }
}scan[maxn*2];
inline void add(int a,int b) { next[++cnt] = side[a]; side[a] = cnt; toit[cnt] = b; }
inline void dfs(int now)
{
size[now] = 1; heavy[now] = N;
for (int i = side[now];i;i = next[i])
{
dfs(toit[i]);
father[toit[i]] = now,size[now] += size[toit[i]];
if (size[toit[i]] > size[heavy[now]]) heavy[now] = toit[i];
}
}
inline void Div(int now,int Top)
{
id[now] = ++tot; top[now] = Top;
if (side[now]) Div(heavy[now],Top);
for (int i = side[now];i;i = next[i])
if (toit[i] != heavy[now]) Div(toit[i],toit[i]);
}
inline void pushdown(int now,int l,int r)
{
if (!inc[now]) return;
if (l < r)
{
int mid = (l + r) >> 1;
tree[now<<1] += (ll)inc[now]*(mid-l+1)%rhl;
tree[now<<1|1] += (ll)inc[now]*(r-mid)%rhl;
if (tree[now<<1] >= rhl) tree[now<<1] -= rhl;
if (tree[now<<1|1] >= rhl) tree[now<<1|1] -= rhl;
inc[now << 1] += inc[now]; inc[now << 1|1] += inc[now];
if (inc[now<<1] >= rhl) inc[now<<1] -= rhl;
if (inc[now<<1|1] >= rhl) inc[now<<1|1] -= rhl;
}
inc[now] = 0;
}
inline void modify(int ql,int qr,int l,int r,int now)
{
pushdown(now,l,r);
if (ql <= l&&r <= qr)
{
tree[now] += r-l+1; inc[now]++;
if (inc[now] >= rhl) inc[now] -= rhl;
if (tree[now] >= rhl) tree[now] -= rhl; return;
}
int mid = (l + r) >> 1;
if (qr <= mid) modify(ql,qr,l,mid,now<<1);
else if (ql > mid) modify(ql,qr,mid+1,r,now<<1|1);
else modify(ql,mid,l,mid,now<<1),modify(mid+1,qr,mid+1,r,now<<1|1);
tree[now] = tree[now<<1]+tree[now<<1|1];
if (tree[now] >= rhl) tree[now] -= rhl;
}
inline int ask(int ql,int qr,int l,int r,int now)
{
pushdown(now,l,r);
if (ql <= l&&r <= qr) return tree[now];
int mid = (l + r) >> 1,ret;
if (qr <= mid) ret = ask(ql,qr,l,mid,now<<1);
else if (ql > mid) ret = ask(ql,qr,mid+1,r,now<<1|1);
else ret = ask(ql,mid,l,mid,now<<1)+ask(mid+1,qr,mid+1,r,now<<1|1);
if (ret >= rhl) ret -= rhl;
return ret;
}
inline void insert(int now)
{
for (;top[now];now = father[top[now]]) modify(id[top[now]],id[now],1,N,1);
modify(id[0],id[now],1,N,1);
}
inline int query(int now)
{
int ret = 0;
for (;top[now];now = father[top[now]])
{
ret += ask(id[top[now]],id[now],1,N,1);
if (ret >= rhl) ret -= rhl;
}
ret += ask(id[0],id[now],1,N,1);
if (ret >= rhl) ret -= rhl;
return ret;
}
int main()
{
freopen("3626.in","r",stdin);
freopen("3626.out","w",stdout);
scanf("%d %d",&N,&Q);
for (int i = 1,a;i < N;++i) scanf("%d",&a),add(a,i);
dfs(0); Div(0,0);
for (int i = 1,l,r,z;i <= Q;++i)
{
scanf("%d %d %d",&l,&r,&z);
scan[(i << 1)-1] = (SCAN){l-1,z,i,-1};
scan[i << 1] = (SCAN){r,z,i,1};
}
sort(scan+1,scan+(Q<<1|1));
for (int i = 1,j = -1;i <= (Q<<1);++i)
{
while (j < N&&j + 1 <= scan[i].pos) insert(++j);
ans[scan[i].ord] += scan[i].sign*query(scan[i].key);
if (ans[scan[i].ord] >= rhl) ans[scan[i].ord] -= rhl;
if (ans[scan[i].ord] < 0) ans[scan[i].ord] += rhl;
}
for (int i = 1;i <= Q;++i) printf("%d\n",ans[i]);
fclose(stdin); fclose(stdout);
return 0;
}
高考结束,重新回归。