树套树

树套树

还有很多东西要补...

\(2024.05.04 ~ UPD:\) 修正了 \(P3810\)空间复杂度 问题(\(O(N \log N) \to O(N \log ^ 2 N)\)

\(k\) 大值

树套树 了,这里面就不写 整体二分离线做法

Luogu P3242 [HNOI2015] 接水果

形式化一下题意

给定一棵 \(N\) 个点的树,现在 树上有 \(P\)关键路径 \(u_i \to v_i\),同时 每条路径有权值 \(c_i\)

\(Q\) 次询问,每次 给出一条路径 \(x_i \to y_i\)

\(x_i \to y_i\) 子路径\(K\)关键路径权值


为方便描述,当 \(u_i \to v_i\)\(x_i \to y_i\)子路径

下文也会称 \(x_i \to y_i\)\(u_i \to v_i\)母路径

\(u\)\(DFS\)\(\to ~ Dfn_u\)\(u\)子树大小 \(\to ~ Siz_u\)

显然我们得先把树 拍平,转化到 序列上,所以首先求一个树的 \(DFS\)

后面的事情无非就是 描述 子路径\(DFS\) 序上的性质

分两种情况

  1. 关键路径 \(u_i \to v_i\) 满足 \(LCA (u_i, v_i) = u_i\)\(LCA (u_i, v_i) = v_i\)

  2. 关键路径 \(u_i \to v_i\) 满足 \(LCA(u_i,v_i) \neq u_i\) 且 $$LCA(u_i,v_i) \neq v_i$$

显然我们发现 \(LCA (u_i, v_i) = u_i\)\(LCA (u_i, v_i) = v_i\) 本质相同

故下文将默认 \(LCA (u_i, v_i) = u_i\) 的情况来讨论


对于这 第一种情况,设有关键路径 \(u_i \to v_i\),路径上 第二个点 \(z_i\)

我们把 能将 \(u_i \to v_i\) 这条路径包含的路径 两端点集称为 \(L_i, R_i\)

即从 \(L_i\)任选点 \(l_j\)\(R_i\) 中 **任选点 **\(r_j\)\(l_j \to r_j\) 一定是 \(u_i \to v_i\)母路径

image

从上图中,我们可以发现 \(L_i\) 其实是以下 两个点集 的并集

  1. 不在 \(u_i\) 子树内的 所有点,即 \(L_i \supseteq [1, Dfn_{u_i})\)

  2. \(u_i\) 子树内,而 不在 \(z_i\) 子树内的点

     即 $L_i \supseteq [Dfn_{u_i}, Dfn_{z_i}) \cup [Dfn_{z_i} + Siz_{z_i}, Dfn_{u_i} + Siz_{u_i})$
    

其中 第二种点集 实际考虑时需要分成 \(DFS\) 序在 \(z_i\) 的前后 两种,也就是并起来的两个点集

注意 不要重复考虑 \(u_i\) 这个点

\(R_i\) 其实是简单的,它就是 \(v_i\) 及其子树内的点,即 \(R_i = [Dfn_{v_i}, Dfn_{v_i} + Siz_{v_i})\)

注意所有的 区间两端开闭情况


对于这 第二种情况,同样设出以上点与点集

image

十分的快乐!显然 \(L_i,R_i\) 分别就是 \(u_i, v_i\) 及其子树内的点

\(L_i = [Dfn_{u_i}, Dfn_{u_i} + Siz_{u_i}), R_i = [Dfn_{v_i}, Dfn_{v_i} + Siz_{v_i})\)


于是我们考虑 离线询问,将询问路径 \(x_i \to y_i\) 两端点转化为 二维平面上一点 \((Dfn_{x_i}, Dfn_{y_i})\)

在上文中 我们讨论了 要成为关键路径的母路径 所需要满足的限制,我们将 限制也放到平面上

这样每个限制 都恰好描述了一个矩形,同时 对应一条关键路径(有一个权值)

一个矩形 \(Rec_j\) 覆盖 询问路径对应的点 \(P_i\) 时,意味着 \(x_i \to y_i\)\(u_j \to v_j\)母路径

所以 原问题 就被转化成 覆盖询问点 \(P_i\) 的矩形中,权值第 \(k\) 小的矩形的权值

容易想到的问题时 在第一种情况下

我们有 三种限制,对应将建出 三个矩形,但都代表的是 同一个关键路径 和 权值

会不会有重复呢?

显然我们发现限制 两两不交,也就是最终的 矩形两两不交

一个询问点 至多被覆盖在其中一个矩形内,故而 正确性无疑

可以使用 树套树 + 扫描线 维护,扫描线扫其中一维(不妨设扫 横轴)

碰到 矩形左边界 时 将其加入,也就是 这条边 对应的区间 对应的权值 \(+1\)

这里也就是有 \(\log N\) 棵内层的 权值线段树 对应权值 \(+1\)

碰到 矩形右边界 时 将其删除,也就是 这条边 对应的区间 对应的权值 \(-1\)

碰到 询问的横坐标 的时候,我们查询其 外层线段树上覆盖其纵坐标的 \(\log N\) 个区间

找到对应的 \(\log N\)内层权值线段树,然后做 多树二分,找第 \(k\) 小值即可

多树二分 即是 由于内层树 结构相同

我们每次查询 每棵树左儿子的值,求和,如果和大于 \(k\)

意味着 最终答案权值左儿子对应权值区间 内,否则则在 右儿子对应权值区间

一直这样 走到叶子,叶子对应的权值就是答案了

代码写的很长...但是跑得还挺快的

\(Tips:\)

保证平面上 点的横坐标小于纵坐标 以及 矩形左边界横坐标小于下边界纵坐标

我们就可以 不用建立横纵向上两个矩形,也不用讨论 \(LCA(u_i, v_i) = v_i\) 的情况

同时可以避免算重贡献


离散化 关键路径的权值内层的权值线段树 空间可行

#include <bits/stdc++.h>

const int MAXN = 65555;
const int LOGN = 50;
const int RLOG = 16;

using namespace std;

struct Edge {
	int to, nxt;
} E[MAXN << 1];

int H[MAXN], tot;

inline void Add_Edge (const int u, const int v) {
	E[++ tot] = {v, H[u]}, H[u] = tot;
	E[++ tot] = {u, H[v]}, H[v] = tot;
}

int DFN[MAXN], SIZ[MAXN], LOG[MAXN], DEP[MAXN], F[MAXN][LOGN];
int N, P, Q, Cnt = 0;

inline int DFS (const int x, const int f) {
	F[x][0] = f, DFN[x] = ++ Cnt, SIZ[x] = 1, DEP[x] = DEP[f] + 1;
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) SIZ[x] += DFS (E[i].to, x);
	return SIZ[x];
}

inline void Init () {
	for (int k = 1; k < RLOG; ++ k) // XIAOCHOU
		for (int i = 1; i <= N; ++ i)
			F[i][k] = F[F[i][k - 1]][k - 1];
	for (int i = 2; i <= (1 << RLOG); ++ i) LOG[i] = LOG[i >> 1] + 1;
}

inline int LCA (int u, int v) {
	if (DEP[u] < DEP[v]) swap (u, v);
	while (DEP[u] != DEP[v]) u = F[u][LOG[DEP[u] - DEP[v]]];
	if (u == v) return u;
	for (int k = LOG[DEP[u]]; k >= 0; -- k)
		if (F[u][k] != F[v][k])
			u = F[u][k], v = F[v][k];
	return F[u][0];
}

inline int FDS (int u, int v) { // 找 u -> v 路径上第二个点,也就是 z
	if (u == v) return 1; // Make DFN + SIZ > N
	if (DEP[u] < DEP[v] + 1) swap (u, v);
	while (DEP[u] != DEP[v] + 1) u = F[u][LOG[DEP[u] - DEP[v] - 1]];
	return u;
}

struct Inv1 {
	int L, R, C;
	
	inline bool operator < (const Inv1 &a) const {
		return C < a.C;
	}
} I1[MAXN];

struct Inv2 {
	int L, R, K, A, id;
	
	inline bool operator < (const Inv2 &a) const {
		return L < a.L;
	} 
} I2[MAXN];

struct REC {
	int LL, LR, RL, RR, C;
	
	inline bool operator < (const REC &a) const {
		return LL == a.LL ? LR < a.LR : LL < a.LL;
	}
} R1[MAXN << 1], R2[MAXN << 1];


inline bool Cmp1 (const Inv1 &a, const Inv1 &b) {
	return a.L < b.L;
}
inline bool Cmp2 (const Inv2 &a, const Inv2 &b) {
	return a.id < b.id;
}
inline bool Cmp3 (const REC &a, const REC &b) {
	return a.LR < b.LR;
}

int Mea = 0;



namespace SegT {
	namespace VSeg {
		struct Node {
			int L, R, lc, rc, v;
		} T[MAXN * LOGN];
		
		#define M ((T[x].L + T[x].R) >> 1)
		
		int cnt = 0; 
		
		inline void Ins (const int v, int &x, const int L = 1, const int R = P) {
			if (!x) x = ++ cnt, T[x].L = L, T[x].R = R;
			T[x].v ++ ;
			if (L == R) return ;
			if (v <= M) Ins (v, T[x].lc, L, M);
			else Ins (v, T[x].rc, M + 1, R);
		}
		
		inline void Del (const int v, int &x, const int L = 1, const int R = P) {
			if (!x) x = ++ cnt, T[x].L = L, T[x].R = R;
			T[x].v -- ;
			if (L == R) return ;
			if (v <= M) Del (v, T[x].lc, L, M);
			else Del (v, T[x].rc, M + 1, R);
		}
	}
	
	struct Node {
		int L, R, rt = 0;
	} T[MAXN << 2];
	
	inline void Build (const int L = 1, const int R = N, const int x = 1) {
		T[x].L = L, T[x].R = R;
		if (L == R) return ;
		Build (L, M, x << 1), Build (M + 1, R, x << 1 | 1);
	}
	
	inline void Ins (const int L, const int R, const int v, const int x = 1) {
		if (L >  T[x].R || T[x].L >  R) return ;
		if (L <= T[x].L && T[x].R <= R) return VSeg::Ins (v, T[x].rt);
		Ins (L, R, v, x << 1), Ins (L, R, v, x << 1 | 1);
	}
	
	inline void Del (const int L, const int R, const int v, const int x = 1) {
		if (L >  T[x].R || T[x].L >  R) return ;
		if (L <= T[x].L && T[x].R <= R) return VSeg::Del (v, T[x].rt);
		Del (L, R, v, x << 1), Del (L, R, v, x << 1 | 1);
	}
	
	
	
	inline int Query (const int v, int k, int x = 1) {
		vector <int> P_;
		while (T[x].L != T[x].R) { // 找到所有覆盖询问点纵坐标的区间(及对应权值线段树)
			P_.push_back (T[x].rt);
			if (v <= M) x = x << 1;
			else x = x << 1 | 1;
		}
		#undef M
		
		P_.push_back (T[x].rt);
		int Tmp = 0, L = 1, R = P, M;
		for (auto i : P_)  Tmp += VSeg::T[i].v;
		if (Tmp < k) return P;
		Tmp = 0;
		
		while (L != R) { // 多树二分
			for (auto i : P_) Tmp += VSeg::T[VSeg::T[i].lc].v;
			M = (L + R) >> 1;
			if (Tmp < k) {L = M + 1, k -= Tmp; for (auto &i : P_) i = VSeg::T[i].rc;} // 记得 k -= Tmp
			else {R = M; for (auto &i : P_) i = VSeg::T[i].lc;}
			Tmp = 0;
		}
		return L;
	}
}

unordered_map <int, int> Mp;
int Val[MAXN];
int Rnk;
int u, v;

int main () {
	
	cin >> N >> P >> Q;
	
	for (int i = 1; i < N; ++ i) cin >> u >> v, Add_Edge (u, v);
	
	DFS (1, 0), SegT::Build (), Init (); // 求 DFS 序,建外层树,预处理 k 级祖先
	
	for (int i = 1; i <= P; ++ i)
		cin >> I1[i].L >> I1[i].R >> I1[i].C; // 输入关键路径
	
	for (int i = 1; i <= Q; ++ i) // 输入询问,转换到 DFS 序
		cin >> I2[i].L >> I2[i].R >> I2[i].K, I2[i].L = DFN[I2[i].L], I2[i].R = DFN[I2[i].R], I2[i].id = i;
	
	for (int i = 1; i <= Q; ++ i) if (I2[i].L > I2[i].R) swap (I2[i].L, I2[i].R); // 保证点的 x < y
	
	sort (I1 + 1, I1 + P + 1);
	
	for (int i = 1; i <= P; ++ i) Mp[I1[i].C] = ++ Rnk, Val[Rnk] = I1[i].C; // 离散化关键路径权值
	
	for (int i = 1; i <= P; ++ i) {
		if (DFN[I1[i].L] > DFN[I1[i].R]) swap (I1[i].L, I1[i].R); // 保证点的 x < y
		if (I1[i].L == LCA (I1[i].L, I1[i].R)) { // 如果 LCA(u_i, v_i) = u_i
			R1[++ Mea] = {1, DFN[I1[i].L], DFN[I1[i].R], DFN[I1[i].R] + SIZ[I1[i].R] - 1, I1[i].C}; // 祖先
			if (DFN[FDS (I1[i].L, I1[i].R)] + SIZ[FDS (I1[i].L, I1[i].R)] <= N) // 子树后
			R1[++ Mea] = {DFN[FDS (I1[i].L, I1[i].R)] + SIZ[FDS (I1[i].L, I1[i].R)], N, DFN[I1[i].R], DFN[I1[i].R] + SIZ[I1[i].R] - 1, I1[i].C};
			if (DFN[FDS (I1[i].L, I1[i].R)] > DFN[I1[i].L] + 1) // 子树之前
			R1[++ Mea] = {DFN[I1[i].L] + 1, DFN[FDS (I1[i].L, I1[i].R)] - 1, DFN[I1[i].R], DFN[I1[i].R] + SIZ[I1[i].R] - 1, I1[i].C};
		} else { // 另外那种情况
			R1[++ Mea] = {DFN[I1[i].L], DFN[I1[i].L] + SIZ[I1[i].L] - 1, DFN[I1[i].R], DFN[I1[i].R] + SIZ[I1[i].R] - 1, I1[i].C};
		}
	}
	
	for (int i = 1; i <= Mea; ++ i) if (R1[i].LL > R1[i].RL) swap (R1[i].LL, R1[i].RL), swap (R1[i].LR, R1[i].RR); // 保证矩形坐标顺序
	
	int Pos1 = 1, Pos2 = 1;
	
	sort (R1 + 1, R1 + Mea + 1); // 限制矩形横坐标排序,方便扫描线
	memcpy (R2, R1, sizeof R1); 
	sort (R2 + 1, R2 + Mea + 1, Cmp3); // 注意到我们 分别 用左边界横坐标和右边界的横坐标 排序,保证插入删除顺序正确
	sort (I2 + 1, I2 + Q + 1); // 询问的点按横坐标排序
	
	for (int i = 1; i <= Q; ++ i) {
		while (R1[Pos1].LL <= I2[i].L && Pos1 <= Mea) SegT::Ins (R1[Pos1].RL, R1[Pos1].RR, Mp[R1[Pos1].C]), ++ Pos1; // 加入矩形
		while (R2[Pos2].LR <  I2[i].L && Pos2 <= Mea) SegT::Del (R2[Pos2].RL, R2[Pos2].RR, Mp[R2[Pos2].C]), ++ Pos2; // 删除矩形
		I2[i].A = SegT::Query (I2[i].R, I2[i].K); //询问
	}
	
	sort (I2 + 1, I2 + Q + 1, Cmp2);
	
	for (int i = 1; i <= Q; ++ i) cout << Val[I2[i].A] << '\n';
	
	return 0;
}

偏序问题

树套树 了,这里面就不写 \(CDQ\) 分治的 离线做法

Luogu P3810 【模板】三维偏序(陌上花开)

复习了一下 偏序问题,还是比较简单的

先考虑 二维偏序,那就是 对于一维排序,然后 数据结构 维护另一维 前缀

比如 静态逆序对数列“顺序”自然有序,我们用 树状数组 维护 数据大小顺序

然后 每遍历到一个数,查询比它大的(显然只是在它前面的),再将它插入 即可

注意到我们没有计算 在它后面比它小的 部分?

还真是!但是如果这样算了的话 答案就会算重,要取一半,也不好做

推广到 三维偏序 就是,对于一维排序,然后数据结构 维护 二维前缀

会想到 二维树状数组,也不是不行,但是 空间 \(O(N ^ 2)\) 会爆!

考虑到我们 实质上只有 \(N\) 组元素,我们可以采用 动态开点线段树 来解决

最后也就是 树状数组动态开点线段树,各自维护一维

空间 \(O(N + N \log ^ 2 N ) = O(N \log ^ 2N)\),时间 \(O(N \log ^ 2 N)\),可以通过

注意到这里空间是 树状数组线段树\(\log ^ 2\)

树状数组 每次改 \(\log\) 个结点,每个节点对应 线段树上 \(\log\) 个结点

用这样的做法也可以拓展到 更高维的偏序问题,不断的套 动态开点线段树 就行

注意到时间是 \(O(N \log ^ k N)\) 的,对于 \(k + 1\) 维偏序

所以 \(OI\) 的数据范围内,在 \(5\) 维以后还不如 \(O(N ^ 2)\),就没什么意义

这个东西是 好写的

#include <bits/stdc++.h>

const int MAXN = 200005;
static int MAXV = 200005;
const int LOGN = 50;

using namespace std;

namespace PO3 {
	#define lowbit(x) (x & -x)
	namespace SegT {
		struct Node {
			int v, lc, rc, L, R;
		} T[MAXN * LOGN];
		
		#define M ((T[x].L + T[x].R) >> 1)
		
		int tot = 0;
		
		inline void Ins (const int v, int &x, int L = 1, int R = MAXV) {
			if (!x) x = ++ tot, T[x].L = L, T[x].R = R;
			++ T[x].v;
			if (L == R) return ;
			if (v <= M) Ins (v, T[x].lc, L, M);
			else Ins (v, T[x].rc, M + 1, R);
		}
		
		inline int Query (const int L, const int R, const int x) {
			if (L > T[x].R || T[x].L > R || !x) return 0;
			if (L <= T[x].L && T[x].R <= R) return T[x].v;
			return Query (L, R, T[x].lc) + Query (L, R, T[x].rc);
		}
	}
	
	namespace BIT {
		struct Node {
			int v, rt;
		} T[MAXN];
		
		inline void Ins (const int b, const int c) {
			for (int i = b; i <= MAXV; i += lowbit (i)) ++ T[i].v, SegT::Ins (c, T[i].rt);
		}
		
		inline int Sum (const int b, const int c) {
			int Ret = 0;
			for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query (1, c, T[i].rt);
			return Ret;
		}
	}
	
	using namespace BIT;
}

namespace Value {
	int N;
	short Ans[MAXN];
	
	struct Node {
		int a, b, c;
		
		inline bool operator < (const Node &x) const {
			return a < x.a;
		}
	} Q[MAXN];
	
	inline void Solve () {

	ios::sync_with_stdio(0);	
	cin.tie(0), cout.tie(0);

		cin >> N >> MAXV;
		
		for (int i = 1; i <= N; ++ i) cin >> Q[i].a >> Q[i].b >> Q[i].c;
		
		sort (Q + 1, Q + N + 1);
		
		int Pos = 1;
		
		for (int i = 1; i <= N; ++ i) {
			while (Q[Pos].a <= Q[i].a && Pos <= N) PO3::Ins (Q[Pos].b, Q[Pos].c), ++ Pos;
			Ans[PO3::Sum (Q[i].b, Q[i].c)] ++;
		}
		
		for (int i = 1; i <= N; ++ i) cout << Ans[i] << '\n';
	}
}

int main () {
	
	Value::Solve ();
	
	return 0;
}

Luogu P3157 [CQOI2011] 动态逆序对

简单修改一下 三维偏序 板子就行

此处偏序的三维就是 删除时间 \(T\),元素位置 \(P\),元素大小 \(K\)

需要满足 \(T_i < T_j, P_i < P_j, K_i > K_j\),或 \(T_i < T_j, P_i > P_j, K_i < K_j\)

我们先在最开始算出 未删除时逆序对数,每次删除减去 对应点贡献的对数 即可

注意这里不能像 静态逆序对 一样 只统计一种情况

因为每次都要回答 当前情况下整个序列的逆序对数

具体做法默认 时间维 排好序

答案从 未删除时逆序对数 每次减去 当前删除点 的两种贡献即可

#include <bits/stdc++.h>

const int MAXN = 200005;
static int MAXV = 200005;
const int LOGN = 50;

using namespace std;

namespace PO3 {
	#define lowbit(x) (x & -x)
	namespace SegT {
		struct Node {
			int v, lc, rc, L, R;
		} T[MAXN * LOGN];
		
		#define M ((T[x].L + T[x].R) >> 1)
		
		int tot = 0;
		
		inline void Ins (const int v, int &x, int L = 1, int R = MAXV) {
			if (!x) x = ++ tot, T[x].L = L, T[x].R = R;
			++ T[x].v;
			if (L == R) return ;
			if (v <= M) Ins (v, T[x].lc, L, M);
			else Ins (v, T[x].rc, M + 1, R);
		}
		
		inline void Del (const int v, int &x, int L = 1, int R = MAXV) {
			if (!x) x = ++ tot, T[x].L = L, T[x].R = R;
			-- T[x].v;
			if (L == R) return ;
			if (v <= M) Del (v, T[x].lc, L, M);
			else Del (v, T[x].rc, M + 1, R);
		}
		
		inline int Query (const int L, const int R, const int x) {
			if (L > T[x].R || T[x].L > R || !x) return 0;
			if (L <= T[x].L && T[x].R <= R) return T[x].v;
			return Query (L, R, T[x].lc) + Query (L, R, T[x].rc);
		}
		
		inline void Print (const int x) {
			if (!x) return ;
			cerr << "x : " << x << " |L, R| : |" << T[x].L << "," << T[x].R << "| v : " << T[x].v << " lc : " << T[x].lc << " rc : " << T[x].rc << endl;
			Print (T[x].lc), Print (T[x].rc);
		}
		
		#undef M
	}
	
	namespace BIT {
		struct Node {
			int v, rt;
		} T[MAXN];
		
		inline void Ins (const int b, const int c) {
			for (int i = b; i <= MAXV; i += lowbit (i)) ++ T[i].v, SegT::Ins (c, T[i].rt);
		}
		
		inline void Del (const int b, const int c) {
			for (int i = b; i <= MAXV; i += lowbit (i)) -- T[i].v, SegT::Del (c, T[i].rt);
		}
	}
	
	using namespace BIT;
	
	inline int Sum (const int b, const int c) {
		int Ret = 0;
		for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query (1, c, T[i].rt);
		return Ret;
	}
	
	inline int Que1 (const int b, const int c) { // < b, > c;
		int Ret = 0;
		for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query (c + 1, MAXV, T[i].rt);
		return Ret;
	}
	
	inline int Que2 (const int b, const int c) { // > b, < c;
		return Sum (MAXV, c) - Sum (b, c);
	}
}

namespace Value {
	int N, M, x;
	int K[MAXN], P[MAXN];
	long long Ans;
	
	
	inline void Solve () {

		cin >> N >> M, MAXV = N;
		
		for (int i = 1; i <= N; ++ i) {
			cin >> K[i], P[K[i]] = i;
			Ans += PO3::Que1 (i, K[i]), PO3::Ins (i, K[i]);
		}
		
		for (int i = 1; i <= M; ++ i) {
			cin >> x;
			cout << Ans << '\n';
			PO3::Del (P[x], x);
			Ans -= PO3::Que1 (P[x], x);
			Ans -= PO3::Que2 (P[x], x);
		}
	}
}

int main () {
	
	Value::Solve ();
	
	return 0;
}

Luogu P3759 [TJOI2017] 不勤劳的图书管理员

题面纯纯依托构式...

使我的大脑旋转

以下给出一个 可能更好理解一点的 形式化题面

给定 \(N\) 个元素 \(x_i\),以及代价 \(c_i\)

\((x_i, x_j)\) 构成 逆序对\(i < j\)\(x_i > x_j\))时,获得 \(c_i + c_j\) 的贡献

现在给定 \(M\) 个操作,每个操作 将交换两个元素 \(x_i, x_j\)(以及对应的 \(c_i, c_j\)

每次操作后整个序列的贡献

注意到这其实就是 动态逆序对 的一种

重点在于 交换数求逆序对,同时 逆序对的贡献不一

交换操作 可以变换为 删除两个元素再加入两个元素(常数 \(\times 4\)

同时统计贡献的时候 我们不止记录逆序对个数(每次 \(+1\)

再记录一个 对应贡献 \(c_i\) 的和,每次计算处 删加后的对应贡献,累积到 \(Ans\)

差不多了,注意取模问题

#include <bits/stdc++.h>

const int V = 50005;
const int MAXN = 100005;
const int LOGN = 100;
const int MOD  = 1000000007;

using namespace std;


int N, M;

struct Book {
	int v, x, id;
	
	inline bool operator < (const Book &a) const {
		return x < a.x;
	} 
} B[MAXN];

int Pos[MAXN];

inline bool Cmp (const Book &a, const Book &b) {
	return a.id < b.id;
}

namespace PO3 {
	
	namespace SegT {
		struct Node {
			long long v;
			int k, lc, rc;
			unsigned short L, R;
		} T[MAXN * LOGN];
		
		#define M ((T[x].L + T[x].R) >> 1)
		
		int tot = 0;
		
		inline void Ins (const int v, int &x, const int delta, const int k, const int L = 1, const int R = V) {
			if (!x) x = ++ tot, T[x].L = L, T[x].R = R;
			(T[x].v += delta) %= MOD, T[x].k += k;
			if (L == R) return ;
			if (v <= M) Ins (v, T[x].lc, delta, k, L, M);
			else Ins (v, T[x].rc, delta, k, M + 1, R);
		}
		
		inline long long Query1 (const int L, const int R, const int x) {
			if (L > T[x].R || R < T[x].L || !x) return 0;
			if (L <= T[x].L && T[x].R <= R) return T[x].v;
			return Query1 (L, R, T[x].lc) + Query1 (L, R, T[x].rc);
		}
		
		inline long long Query2 (const int L, const int R, const int x) {
			if (L > T[x].R || R < T[x].L || !x) return 0;
			if (L <= T[x].L && T[x].R <= R) return T[x].k;
			return Query2 (L, R, T[x].lc) + Query2 (L, R, T[x].rc);
		}
		
		#undef M
	}
	
	#define lowbit(x) (x & -x)
	
	namespace BIT {
		int T[MAXN];
		
		inline void add (const int b, const int c, const int delta) {
			for (int i = b; i <= V; i += lowbit (i)) SegT::Ins (c, T[i], + delta, + 1);
		}
		
		inline void del (const int b, const int c, const int delta) {
			for (int i = b; i <= V; i += lowbit (i)) SegT::Ins (c, T[i], - delta, - 1);
		}
	}
	
	inline long long Sum1 (const int b, const int c) {
		long long Ret = 0;
		for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query1 (1, c, BIT::T[i]);
		return Ret;
	}
	
	inline long long Sum3 (const int b, const int c) {
		long long Ret = 0;
		for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query2 (1, c, BIT::T[i]);
		return Ret;
	}
	
	inline long long Sum2 (const int b, const int c) {
		long long Ret = 0;
		for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query1 (c + 1, N, BIT::T[i]);
		return Ret;
	}
	
	inline long long Sum4 (const int b, const int c) {
		long long Ret = 0;
		for (int i = b; i; i -= lowbit (i)) Ret += SegT::Query2 (c + 1, N, BIT::T[i]);
		return Ret;
	}
	
	inline long long Del (const int b, const int c, const int delta) {
		long long Ret = Sum2 (b, c) + Sum1 (N, c) - Sum1 (b, c);
		int ret = Sum4 (b, c) + Sum3 (N, c) - Sum3 (b, c);
		BIT::del (b, c, delta);
		return Ret + 1ll * ret * delta;
	}
	
	inline long long Add (const int b, const int c, const int delta) {
		long long Ret = Sum2 (b, c) + Sum1 (N, c) - Sum1 (b, c);
		int ret = Sum4 (b, c) + Sum3 (N, c) - Sum3 (b, c);
		BIT::add (b, c, delta);
		return Ret + 1ll * ret * delta;
	}
	
	inline long long Swap (int x, int y) {
		long long Ret = 0, Tmp;
		Ret -= Del (B[x].x, B[x].id, B[x].v), Ret = Ret;
		Ret -= Del (B[y].x, B[y].id, B[y].v), Ret = Ret;
		Tmp = B[x].x, B[x].x = B[y].x, B[y].x = Tmp;
		Tmp = B[x].v, B[x].v = B[y].v, B[y].v = Tmp;
		Ret += Add (B[x].x, B[x].id, B[x].v), Ret = Ret;
		Ret += Add (B[y].x, B[y].id, B[y].v), Ret = Ret;
		return Ret;
	}
	
}

inline void SetAns (long long &a) {
	while (a > MOD) a -= MOD;
	while (a < 0) a += MOD;
}

namespace Value {
	int u, v;
	long long Ans = 0;
	
	inline void Solve () {
		cin >> N >> M;
		
		for (int i = 1; i <= N; ++ i) cin >> B[i].x >> B[i].v, B[i].id = i;
		
		for (int i = 1; i <= N; ++ i) Ans += PO3::Add (B[i].x, B[i].id, B[i].v);
		
		for (int i = 1; i <= M; ++ i) 
			cin >> u >> v, Ans = Ans + PO3::Swap (u, v), SetAns (Ans), cout << Ans << '\n';
	}
}

signed main () {
	
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	Value::Solve ();
	
	return 0;
}
posted @ 2024-05-03 21:52  FAKUMARER  阅读(12)  评论(0编辑  收藏  举报