XSY3490 / ZROI P618 广义线段树 + 莫队 + 点分树
\(Solution\)
为了与普通区间区分,我们称线段树上某个结点 \([l, r]\) 为 块 \([l, r]\)
考虑模拟线段树区间查询 \([l, r]\) 时下放到的底部端点,必然是一堆连续的、不包含 \(l, r\) 区间的整块,和两个(或者是一整个)以 \(l, r\) 为左右端点的小散块(这里整块和散块没有大小性质区分,只是为了区分端点区间和内部区间)。
若一个块 \([x, y]\) 具有性质 \(N\),则其为所有左端点为 \(x\) 的块中的最大块
若一个块 \([x, y]\) 具有性质 \(M\),则其为所有右端点为 \(y\) 的块中的最大块
引理:除根结点外,其余所有点均恰好满足 \(N\) 性质或 \(M\) 性质的其中一条。
证明:考虑某个结点 \([l, r]\) 其必然至少满足一条性质,而其父节点必然包含了其某一端点,所以至多只满足一条,由此得到唯一性。
更进一步的,区间 \([l, r]\) 覆盖到的所有块,左边一块连续的必然满足性质 \(N\),右边一块必然满足性质 \(M\)(\([1, n]\) 比较特殊)。
那么显然有个转化就出来了
设 \(maxl_i, maxr_i\) 为以 \(i\) 为线段左端点的块的最大的右端点,和 以 \(i\) 为线段右端点的线段的最小的左端点。
构建两颗新树 \(L, R\),在 \(L\) 中连边 \((maxl_i+1, i)\),\((maxr_i-1, r)\) 边权分别是对应线段的标号。
考虑一个区间 \([a, b]\) 映射到树 \(L, R\) 上,设指针 \(u=a/b\) 从 \(L/R\) 树上往上跳,直到到根节点或父结点 \(u>b/u<a\) 为止(准确来说是跳到 \(maxl_{x}>b/maxr_{x}<a\) 的第一个点 \(x\)),那么这个区间映射到线段树的编号集合就是向上跳过程中经过的边权集合,注意到这些边在 \(L/R\) 树上必然各连成一条链(注意 \([1, n]\) 只需要取一边即可)。
处理询问,首先将 \([a, b]\) \([c, d]\) 各自映射,假设得到集合为 \(S^l_{[a, b]},S^r_{[a, b]},S^l_{[c, d]},S^r_{[c, d]}\),那么现在需要考虑的就是前二者对后二者贡献,将连差分成到根节点的路径计算。
现在问题就是,一堆询问,求两个路径之间点对距离的和,rush 成莫队,变成点到路径上点的距离和,点分树暴力算即可。
具体的,我们将树拍成括号序(dfs 进一次(左括号添加贡献),所有子树遍历完再进入一次(右括号删除贡献)),由于我们已经做了路径差分,所以实际上在括号序上表现为两个前缀的点的路径和,用 \(p_{L}, p_{R}\) 来表示询问的位置,那么我们可以将所有可能的询问 \((p_{L}, p_{R})\) 看成一种区间询问,当成莫队做。
当我们右移指针(不管是 \(p_{L}\) 还是 \(p_{R}\))时,相当于加入一个贡献点(可能是右括号),左移则相当于删除,这是与正常莫队不一样的点。
移动和删除计算贡献时我们直接对 \(B\) 树进行点分树计算即可,复杂度为 \(O(\log n)\)。
那么总复杂度为 \(O(n\sqrt{m}\log n)\)。
发现括号序版的树上莫队常数大得离谱。。。
Code:
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#pragma GCC optimize(2)
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <cctype>
#include <vector>
#include <queue>
#include <cmath>
#include <bitset>
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){if(x>=cp) x-=cp;if(x<0) x+=cp;return x;}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
char ch=getchar();int x=0, f=1;
while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
inline void write(int x){
if(x<0) putchar('-'), x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
int ret=1;
for(; b; b>>=1, a=1ll*a*a%cp)
if(b&1) ret=1ll*ret*a%cp;
return ret;
}
const int N=1e4+5;
const int S=N<<1;
const int M=1e5+5;
namespace Btree{
vi G[S];int ST[20][S], dfn[S], dep[S], fa[S];
int MX(int x, int y){return dep[x]<dep[y]?x:y;}
int LCA(int x, int y){
if(x==y) return x;x=dfn[x], y=dfn[y];if(x>y) swap(x, y);
int k=__lg(y-x);return fa[MX(ST[k][x+1], ST[k][y-(1<<k)+1])];
}
int dist(int x, int y){if(!x||!y) return 0;return dep[x]+dep[y]-2*dep[LCA(x, y)];}
void dfs(int x){ST[0][dfn[x]=++dfn[0]]=x;for(auto v:G[x]) if(!dep[v]) dep[v]=dep[x]+1, fa[v]=x, dfs(v);}
int tim, siz[S], vis[S], dfa[S], Mn, g;
void findrt(int x, int all){
int mx=0;siz[x]=1, vis[x]=tim;
for(auto v:G[x]) if(vis[v]<tim) findrt(v, all), siz[x]+=siz[v], mx=max(mx, siz[v]);
mx=max(mx, all-siz[x]);if(mx<Mn) Mn=mx, g=x;
}
int mxd;
void dfz(int rt, int dep=0){
mxd=max(mxd, dep);++tim;findrt(rt, 114514);++tim;vis[rt]=INF;for(auto v:G[rt])
if(vis[v]<tim) Mn=siz[v], g=v, findrt(v, siz[v]), dfa[g]=rt, dfz(g, dep+1);
}
void init(int nd){
for(int i=1, u, v; i<nd; ++i)
u=read(), v=read(), G[u].pb(v), G[v].pb(u);
dfs(dep[1]=1);
for(int i=1; i<=__lg(nd); ++i)
for(int x=1; x+(1<<i)-1<=nd; ++x)
ST[i][x]=MX(ST[i-1][x], ST[i-1][x+(1<<i-1)]);
dfz(1);//printf("!!!!%d\n", mxd);
// for(int i=1; i<=nd; ++i) printf("%d ", dfa[i]);puts("");
}
}
int n, m, nd;
struct Tree{
int rt, fa[N][20], w[N], bs[S], top, pos[N];vi G[N];
void dfs(int x){
for(int i=1; i<20; ++i) fa[x][i]=fa[fa[x][i-1]][i-1];
if(x^rt) bs[pos[x]=++top]=w[x];//, printf("%d ", w[x], x);
for(auto v:G[x]) dfs(v);if(x^rt) bs[++top]=-w[x];//, printf("%d ", -w[x], x);
}
void init(){for(int i=1; i<=n; ++i) G[fa[i][0]].pb(i);fa[rt][0]=rt;dfs(rt);}
}L, R;
void find(int a, int b, int &u, int &v){
u=a, v=b;//if(a==1&&b==n) return (void)(u=n+1);//[1, n] 会重复,只用其中一个就行了(但是数据好像造假了啊)
for(int i=19; i>=0; --i){
if(L.fa[u][i]<=b) u=L.fa[u][i];
if(R.fa[v][i]>=a) v=R.fa[v][i];
}
if(L.fa[u][0]-1==b) u=L.fa[u][0];
if(R.fa[v][0]+1==a) v=R.fa[v][0];
}
int p;//2n-1
void build(int k, int l, int r){
if(l<r){
int mid=read();
build(++p, l, mid);build(++p, mid+1, r);
}
L.fa[l][0]=r+1, L.w[l]=k, R.fa[r][0]=l-1, R.w[r]=k;
}
ll ans[M];int B;
struct ask{int l, r, qid, op;};
bool cmp(const ask a, const ask b){
if(a.l/B==b.l/B) return ((a.l/B)&1)?a.r<b.r:a.r>b.r;
else return a.l<b.l;
}
vector <ask> Q[3];//00-LL 01-LR 11 RR
void add(int l, int opl, int r, int opr, int id, int op){
if(!opr) swap(l, r), swap(opl, opr);/*10 变成 01*/int opx=opl+opr;
l=opx^2?L.pos[l]:R.pos[l], r=opx^0?R.pos[r]:L.pos[r];
if(l&&r) Q[opx].pb((ask){l, r, id, op});//如果是根的话就不需要算了}
}
ll res;struct node{ll sum, sumf;int x, cnt;}f[S][2];
void upd(int sid, int p, int op){
int xp=(p>0?1:-1), x=p<0?-p:p;
for(int u=x, lst=0; u; ){
int disu=Btree :: dist(u, x);
res+=1ll*op*xp*((f[u][sid^1].sum-f[lst][sid^1].sumf)+
1ll*disu*(f[u][sid^1].cnt-f[lst][sid^1].cnt));
node *fu=&f[u][sid];fu->cnt+=op*xp, fu->sum+=op*xp*disu;
lst=u, u=Btree :: dfa[u];fu->sumf+=op*xp*Btree :: dist(u, x);
}
}
void MoA(int tp){//莫队
res=0;for(int i=1; i<=nd; ++i) f[i][0]=f[i][1]=(node){0, 0, i, 0};
int q=Q[tp].size();if(!q) return ;B=2*nd/sqrt(3*q)+25;
sort(Q[tp].begin(), Q[tp].end(), cmp);
int l=0, r=0;
for(auto me:Q[tp]){
int x;
while(l<me.l) ++l, x=tp^2?L.bs[l]:R.bs[l], upd(0, x, 1);
while(l>me.l) x=tp^2?L.bs[l]:R.bs[l], upd(0, x, -1), --l;
while(r<me.r) ++r, x=tp^0?R.bs[r]:L.bs[r], upd(1, x, 1);
while(r>me.r) x=tp^0?R.bs[r]:L.bs[r], upd(1, x, -1), --r;
ans[me.qid]+=me.op*res;
}
}
signed main(){
// freopen("hack.in", "r", stdin);
// freopen("hack.out", "w", stdout);
n=read(), m=read(), nd=2*n-1;L.rt=n+1, R.rt=0;build(p=1, 1, n);
L.init(), R.init();Btree :: init(nd);
for(int i=1; i<=m; ++i){
int a=read(), b=read(), u, v;find(a, b, u, v);//a-u b-v
int c=read(), d=read(), x, y;find(c, d, x, y);//c-x d-y
if(a>b||c>d) exit(0);
add(a, 0, c, 0, i, 1);add(u, 0, c, 0, i, -1);
add(u, 0, x, 0, i, 1);add(a, 0, x, 0, i, -1);
add(a, 0, d, 1, i, 1);add(u, 0, d, 1, i, -1);
add(u, 0, y, 1, i, 1);add(a, 0, y, 1, i, -1);
add(b, 1, c, 0, i, 1);add(v, 1, c, 0, i, -1);
add(v, 1, x, 0, i, 1);add(b, 1, x, 0, i, -1);
add(b, 1, d, 1, i, 1);add(v, 1, d, 1, i, -1);
add(v, 1, y, 1, i, 1);add(b, 1, y, 1, i, -1);
//16 个询问,堂堂完结!
}
MoA(1), MoA(0), MoA(2);
for(int i=1; i<=m; ++i) printf("%lld\n", ans[i]);
return 0;
}