题解 P6782 【[Ynoi2008]rplexq】
skydogli
2020-09-10 12:13:37
![](https://cdn.luogu.com.cn/upload/image_hosting/r16obpqh.png)
思路清晰,但是难写得和个鬼一样。~~可能是我码力太差了吧~~
观察题面,我们平时求这种答案得用DP求,所以应该不好用正常的方法直接算。
但是对于新增或删除一个点的贡献好像是可以计算的,于是我们可以用莫队算法,每次更新点时$ O(dep_x) $更新即可,复杂度是相当优越的 $O(n^2\sqrt{n})$ 。不难发现这是一个点到根的路径,所以考虑用重链剖分优化,这样我们就可以直接计算轻边的贡献了,但是对于重子树的答案依然很难求。
回顾DP的方式,对于一个节点 $x$ 的所有儿子 $v_{1\dots k}$,答案就是 $\sum\limits_{i=1}^n sum_{i-1}\times siz_{v_i}$ ,其中 $sum$ 表示儿子子树大小的前缀和。也就是我们可以钦定 $x$ 的重儿子是最后一个儿子(即最后处理),莫队时只维护轻儿子的子树大小之和以及答案,最后查重儿子只需要二维数点后计算贡献即可。这样我们的复杂度就优化到 $O(n\sqrt{n}\log n)$了。
然后去看看数据范围,2e5,1s,显然过不了。
接下来就要来点阴间优化了。
我们发现目前莫队和二维数点的复杂度分别是 $O(n\sqrt{n}\log n)$ 和 $O(n\log n)$,这显然不是lxl想要的,所以我们考虑能不能对于轻子树的修改再少一点,二维数点计算多一点点,于是就多存几个重儿子试试看。如果存 $k$ 个重儿子,那么莫队复杂度就是$O(n\sqrt{n}log_k n)$,由于查询很多,所以我们数点的数据结构应该换成 $O(\sqrt{n})$ 修改,$O(1)$ 查询的分块,所以数点复杂度是$O(n\sqrt{n}+qk)$,不难发现 $k={n}^d(d\in(0,0.5])$ 时整体复杂度就变成了 $O(n\sqrt{n})$ 。这个 $d$ 显然要根据代码常数来决定。
想到这里,我心潮澎湃地码了起来,然后获得了21分的好成绩!
这是我的最初代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int read(){
int a=0;char c=getchar();
while(c>'9'||c<'0')c=getchar();
while('0'<=c&&c<='9'){
a=a*10+c-48;
c=getchar();
}
return a;
}
#define MN 200005
int n,m,rt,blk;
struct data{
int l,r,x,id;
}q[MN];
bool CMP(data a,data b){
if(a.l/blk==b.l/blk)return a.r<b.r;
return a.l<b.l;
}
int fa[MN],siz[MN],top[MN],id[MN],cnt;
vector<int>w[MN],edge[MN];
bool cmp(int a,int b){return siz[a]>siz[b];}
void dfs(int x){
siz[x]=1;
for(int i=0;i<edge[x].size();++i){
int v=edge[x][i];
if(fa[x]==v)continue;
fa[v]=x;
dfs(v);
siz[x]+=siz[v];
}
sort(edge[x].begin(),edge[x].end(),cmp);
int N=min(blk,(int)edge[x].size());
for(int i=0;i<N;++i){
int v=edge[x][i];
if(fa[x]==v)continue;
w[x].push_back(v);
}
}
void DFS(int x){
id[x]=++cnt;
// cerr<<"DFS: "<<x<<" "<<cnt<<endl;
int N=min(blk,(int)edge[x].size());
for(int i=0;i<N;++i){
int v=edge[x][i];
if(fa[x]==v)continue;
top[v]=top[x];
DFS(v);
}
for(int i=N;i<edge[x].size();++i){
int v=edge[x][i];
if(fa[x]==v)continue;
top[v]=v;
DFS(v);
}
}
//lt:轻的
//SIZ:顶端的子树大小
int ltans[MN],ltsiz[MN],SIZ[MN],_siz[MN],ans[MN],T;
vector<int>tag[MN];
void upd(int x,int op){
if(op==1)ltans[x]+=ltsiz[x]*op;
else ltans[x]-=ltsiz[x]-1;
ltsiz[x]+=op;
// cerr<<"?? "<<x<<" "<<ltsiz[x]<<endl;
while(x){
int p=fa[top[x]];
if(p){
ltans[p]+=(ltsiz[p]-SIZ[top[x]])*op;
ltsiz[p]+=op;
// cerr<<"?upp: "<<x<<" "<<p<<" "<<ltsiz[p]<<endl;
// if(p==10)cerr<<"FK!: "<<w[p].size()<<" "<<w[p][0]<<" "<<top[x]<<endl;
}
SIZ[top[x]]+=op;
x=p;
}
}
int a[MN],pre[MN],suf[MN],sum[1005];
vector<int>FKLXL[MN];
int loc(int x){return (x-1)/blk+1;}
void ins(int x,int v){
a[x]+=v;
int pos=loc(x),l=(pos-1)*blk+1,r=min(pos*blk,n);
for(int i=pos;i<=blk;++i)sum[i]+=v;
for(int i=x;i<=r;++i)pre[i]+=v;
for(int i=x;i>=l;--i)suf[i]+=v;
}
int qry(int l,int r){
// cerr<<"FK: "<<l<<" "<<r<<endl;
int pl=loc(l),pr=loc(r);
int res=0;
if(pl<pr)res=sum[pr-1]-sum[pl];
else return pre[r]-pre[l]+a[l];
return res+suf[l]+pre[r];
}
signed main(){
n=read();m=read();rt=read();
blk=sqrt(n)+1;
for(int i=1;i<n;++i){
int u=read(),v=read();
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(rt);top[rt]=rt;DFS(rt);
for(int i=1;i<=m;++i){
q[i].l=read(),q[i].r=read(),q[i].x=read();q[i].id=i;
}
sort(q+1,q+1+m,CMP);
for(int i=1;i<=m;++i){
tag[q[i].l-1].push_back(i);
tag[q[i].r].push_back(i);
}
int l=1,r=0;
for(int i=1;i<=m;++i){
while(q[i].l<l)upd(--l,1);
while(r<q[i].r)upd(++r,1);
while(q[i].l>l)upd(l++,-1);
while(r>q[i].r)upd(r--,-1);
// cerr<<"FK: "<<q[i].x<<" "<<l<<" "<<r<<": "<<ans[q[i].id]<<endl;
ans[q[i].id]=ltans[q[i].x];
_siz[q[i].id]=ltsiz[q[i].x];
// cerr<<"OHH "<<q[i].id<<" x:"<<q[i].x<<" "<<ltsiz[q[i].x]<<" lr:"<<l<<" "<<r<<endl;
// assert(!ltans[q[i].x]);
}
for(int i=1;i<=n;++i){
ins(id[i],1);
for(int j=0;j<tag[i].size();++j){
int ii=tag[i][j],I=q[tag[i][j]].id,x=q[ii].x;
// cerr<<"qwq "<<i<<" "<<I<<" sz: "<<_siz[I]<<" "<<x<<endl;
int a=0,s=_siz[I];
for(int k=0;k<w[x].size();++k){
int v=w[x][k],cnt=qry(id[w[x][k]],id[w[x][k]]+siz[w[x][k]]-1);
// if(x==4)cerr<<"wdnmd: "<<w[x][k]<<" "<<cnt<<" "<<s<<endl;
assert(cnt>=0);
if(i==q[ii].l-1){
FKLXL[ii].push_back(cnt);
}
else{
// cerr<<"?? "<<FKLXL[ii].size()<<" "<<k<<endl;
if(FKLXL[ii].size())cnt-=FKLXL[ii][k];
a+=s*cnt;
s+=cnt;
}
}
if(i==q[ii].l-1){
// ans[I]-=a;
// cerr<<i<<" de "<<I<<" "<<a<<endl;
}
else {
// cerr<<"FK: "<<I<<" "<<i<<" "<<q[ii].l<<" "<<q[ii].r<<endl;
assert(i==q[ii].r);
ans[I]+=a;
// cerr<<i<<" ad "<<I<<" "<<a<<endl;
}
}
}
for(int i=1;i<=m;++i)printf("%lld\n",ans[i]);
return 0;
}
```
~~你竟然不精细实现就想过Ynoi题?~~
而且还有一个问题:查询时一共有 $n\sqrt{n}$ 次查询子树,而且此题要差分来获取子树的具体大小(答案无法差分)所以正常写空间是 $O(n\sqrt{n})$ 的。
只好请教毒瘤本人,发现他的写法是枚举```dfn```序,然后我就yy了个空间复杂度 $O(n)$ 的做法:
考虑重儿子的子树区间```dfn```序连续,也就是我这次的询问等于下次要减去的值,这样的话只要记录每次查询时上次求的值是多少即可。那么我怎么确定我要在```dfn```序为 $i$ 时回答哪些查询呢?我们记录 $i$ 为哪些节点子树```dfn```序的最后一个点,**回答关于这些节点的父亲的查询**,这样的话我们只需要在询问的 $x$ 上打标记即可。
然后卡亿点点常就可以通过本题了。卡常时注意不要在非瓶颈处浪费过多时间。
最终代码可以私戳我(但是好像交10次只能过1次),在我退役之前每天都会在线的。