話說這貨不去切eJOI在這干啥子呢(
UOJ題目頁面傳送門
有一棵大小為\(n\)的樹,根為\(1\),節點\(i\)有一個權值\(a_i\)。支持\(3\)種\(q\)次操作:
- \(\texttt1\ x\ y\ v\):令所有在路徑\(x\to y\)上的點的權值增加\(v\),保證\(v=\pm1\);
- \(\texttt2\ x\ y\):求路徑\(x\to y\)上權值\(>0\)的點數;
- \(\texttt3\ x\):求子樹\(x\)內權值\(>0\)的點數。
強制在線。
\(n,q\in\left[1,10^5\right],a_i\in\left[-10^9,10^9\right]\)。
一眼重剖。於是轉化為線性結構上的區間修改和區間查詢。然后就不會了
看到區間查詢排名,想到線段樹套平衡樹,但是這是區間修改,歇的了。
於是,分塊是無所不能的(
注意到一個性質,由於\(v=\pm1\),所以若\(a_i\leq-q\)或\(a_i\geq q+1\),則\([a_i>0]\)永遠無法改變。所以不妨等效地將\(<-q\)的賦成\(-q\),將\(>q+1\)的賦成\(q+1\),此時值域\(\mathrm O(q)\)。
考慮將線性結構\(a\)分成\(sz1\)塊,每塊\(i\)內維護后綴計數\(cnT_i\),\(cnT_{i,j}\)表示塊\(i\)內\(\geq j\)的數的個數。再維護一個整體增加標記\(add_i\),表示該塊被整體增加過多少。
-
區間修改:對於兩邊不滿的塊,暴力修改,每個數\(\pm1\)的話\(cnT\)是可以\(\mathrm O(1)\)更新的(當然你重構我也不攔你)。對於中間的整塊們,直接修改它們的整體增加標記。\(\mathrm O\!\left(sz1+\dfrac n{sz1}\right)\)。
-
區間查詢:對於兩邊不滿的塊,暴力計數,注意每個數真正的值是它在\(a\)數組中的值加上所在塊的整體增加標記。對於中間的整塊\(i\)們,調用后綴計數,將\(cnT_{i,1-add_i}\)累加進結果即可。最終答案為兩種結果加起來。\(\mathrm O\!\left(sz1+\dfrac n{sz1}\right)\)。
此時令\(sz1=\left\lfloor\sqrt n\right\rfloor\),加上重剖的\(\log\)即可\(\mathrm O\!\left(q\sqrt n\log n\right)\)完美滾粗。當然往死里卡還是能卡過去的,我才不會告訴你我曾經就卡過去了呢(
接下來用出題人智商分析法。如果僅限於此,那么這題不就成了強行上樹了?怎么還能成為集訓隊作業2018呢?所以這個重剖肯定有深藏不露之處。
事實上:無論是對於區間修改還是區間查詢,對整塊處理的時間復雜度顯然是整塊的個數\(\mathrm O\!\left(\dfrac n{sz1}\right)\)。一次分塊維護\(\mathrm O\!\left(\dfrac n{sz1}\right)\),那么一條鏈是不是就需要\(\mathrm O\!\left(\dfrac n{sz1}\log n\right)\)了呢?不,不是。因為重剖出來的區間們不會有交集,那么總的整塊的個數依然是\(\mathrm O\!\left(\dfrac n{sz1}\right)\)級別的。於是一條鏈的復雜度就是\(\mathrm O\!\left(sz1\cdot\log n+\dfrac n{sz1}\right)\)。注意到兩項乘積依然為常數,令\(sz1\cdot\log n=\dfrac n{sz1}\)解得\(sz1=\sqrt{\dfrac n{\log n}}\)(令\(sz1=\left\lfloor\sqrt{\dfrac n{\log_2n}}\right\rfloor\)),此時總時間復雜度為\(\mathrm O\!\left(q\sqrt{n\log n}\right)\)。
然鵝這樣空間復雜度為\(\mathrm O\!\left(q\sqrt{n\log n}\right)\),雖然ML很大但還是超過了一倍。注意到\(cnT\)的值們不會很大,用short
存可以說完美,不多不少剛剛好。
常數還是有點大,不過開個O3就AC了。
代碼:
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=100000,QU=100000,DB_SZ=1300;
int n,qu;
bool ol;
vector<int> nei[N+1];
int fa[N+1],sz[N+1],wson[N+1],dep[N+1],top[N+1],dfn[N+1],nowdfn,mxdfn[N+1];
void dfs1(int x=1){//重剖預處理,下同
sz[x]=1;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa[x])continue;
fa[y]=x;
dep[y]=dep[x]+1;
dfs1(y);
sz[x]+=sz[y];
if(sz[y]>sz[wson[x]])wson[x]=y;
}
}
void dfs2(int x=1,int t=1){
dfn[x]=mxdfn[x]=++nowdfn;
top[x]=t;
if(wson[x])dfs2(wson[x],t),mxdfn[x]=mxdfn[wson[x]];
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa[x]||y==wson[x])continue;
dfs2(y,y);mxdfn[x]=mxdfn[y];
}
}
int a[N+1];
struct dvdblk{//分塊
int sz,sz1;
struct block{int l,r,add;short cnT[2*QU+2];}blk[DB_SZ];
#define l(p) blk[p].l
#define r(p) blk[p].r
#define cnT(p) blk[p].cnT
#define add(p) blk[p].add
void bldblk(int p,int l,int r){//構造一個塊
l(p)=l;r(p)=r;
add(p)=0;
for(int i=l;i<=r;i++)cnT(p)[a[i]+qu]++;
for(int i=2*qu;~i;i--)cnT(p)[i]+=cnT(p)[i+1];
}
void init(){//分塊初始化
sz1=max(1,min(n,int(sqrt(n/max(1.,log2(n))))));
// printf("sz1=%d\n",sz1);
sz=(n+sz1-1)/sz1;
for(int i=1;i<=sz;i++)bldblk(i,(i-1)*sz1+1,min(n,i*sz1));
}
void _add(int l,int r,int v){//區間修改
int pl=(l+sz1-1)/sz1,pr=(r+sz1-1)/sz1;
if(pl==pr){//不滿的塊
for(int i=l;i<=r;i++)
if(v==-1){if(a[i]>-qu)cnT(pl)[a[i]-- +qu]--;}
else{if(a[i]<qu+1)cnT(pl)[++a[i]+qu]++;}
return;
}
//整塊
for(int i=pl+1;i<pr;i++)add(i)+=v;
_add(l,r(pl),v);_add(l(pr),r,v);
}
int grt0(int l,int r){//區間查詢
int pl=(l+sz1-1)/sz1,pr=(r+sz1-1)/sz1;
if(pl==pr){//不滿的塊
int res=0;
for(int i=l;i<=r;i++)res+=a[i]+add(pl)>0;
// cout<<l<<" "<<r<<":"<<res<<"\n";
return res;
}
//整塊
int res=0;
for(int i=pl+1;i<pr;i++)res+=cnT(i)[max(-qu,min(qu+1,1-add(i)))+qu];
return res+grt0(l,r(pl))+grt0(l(pr),r);
}
}db;
void add_chn(int x,int y,int v){//鏈修改
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
db._add(dfn[top[x]],dfn[x],v);
x=fa[top[x]];
}
if(dep[x]<dep[y])swap(x,y);
db._add(dfn[y],dfn[x],v);
}
int grt0_chn(int x,int y){//鏈查詢
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res+=db.grt0(dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
if(dep[x]<dep[y])swap(x,y);
return res+db.grt0(dfn[y],dfn[x]);
}
int grt0_subt(int x){return db.grt0(dfn[x],mxdfn[x]);}//子樹查詢
int main(){
// cout<<sizeof(db)/1024/1024;
cin>>n>>qu>>ol;
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
nei[x].pb(y);nei[y].pb(x);
}
dfs1();dfs2();//重剖
for(int i=1;i<=n;i++){
scanf("%d",a+dfn[i]);
a[dfn[i]]=max(-qu,min(qu+1,a[dfn[i]]));//限制值域
}
db.init();//分塊初始化
int lasans=0;
for(int i=1;i<=qu;i++){
int tp,x,y,z;
scanf("%d%d",&tp,&x);ol&&(x^=lasans);
if(tp==1)scanf("%d%d",&y,&z),ol&&(y^=lasans),add_chn(x,y,z);
else if(tp==2)scanf("%d",&y),ol&&(y^=lasans),printf("%d\n",lasans=grt0_chn(x,y));
else printf("%d\n",lasans=grt0_subt(x));
}
return 0;
}