2024西安铁一中集训DAY23 ---- 模拟赛(类括号匹配dp + baka‘s trick 优化双指针 + 组合数学/高斯消元 + 图上性质题)

前言

感觉是开始集训以来最难的一场了,一道题也没切。但是学到了很多东西。

时间安排及成绩

  • 7:40 开题
  • 7:40 - 7:50 把T1读完了。看到区间+1操作想到差分,但发现差分好像维护不了一点。数据规模感觉是 n 3 m n^3m n3m 的复杂度。会不了一点。
  • 7:50 - 8:10 想T1。尝试了很多dp状态,但感觉还是没法转移。但是仍然没有一点头绪。先跳了。
  • 8:10 - 8:15 看T2,看懂题之后立刻想到了差分然后找最长的区间满足区间gcd大于1。想到了二分加线段树,但是加上求gcd的复杂度总复杂度到了 O ( n l o g 2 3 n ) O(nlog^3_2n^) O(nlog23n)。 我现在一分都没有,所以直接开写。
  • 8:15 - 8:45 把暴力的思路写完了,有点细节。但是把样例都过了,大样例跑的很慢但是跑出来了。
  • 8:45 - 9:20 冷静思考一下,发现可以线段树二分剪掉一个log,但是应该很不好写。写到一半发现我就是糖,直接写ST表不也能剪掉一个log吗。所以花了10min改成st表了,样例也都过了。
  • 9:20 - 9:40 发现不用二分,直接双指针就行。我以为又剪掉一个log,变成单log了,美滋滋的改完了。但是忘了st表建表复杂度是双log的(求gcd)还有一个 log。
  • 9:40 - 10:10 疯狂想T1,突然发现好像可以沿用差分的思路。就是记录当前还有多少+1的左端点没有对应一个-1的端点。那么这样就可以转移了。算算复杂度好像是 ( n × m 4 ) (n \times m^4) (n×m4) 的,应该有 70 p t s 70pts 70pts,并且有很大优化空间。
  • 10:10 - 10:40 把T1暴力写完了,中间还有点小难题,但是被解决了。除了大样例都过了。看见式子比较复杂,化简应该不是很容易。所以先开后面。
  • 10:40 - 11:00 把T3看完了,感觉组合数学式子的化简不是太会。想了一会儿先跳了。看到T4发现有50分是简单的,果断开写。
  • 11:00 - 11:30 把T4的部分分调出来了。然后接着想T3。
  • 11:30 - 11:45 还是不咋会大的部分分,所以写了20分。
  • 11:45 - 12:00 写完后摆烂。

估分:70 + 100 + 20 + 50 = 240
得分:65 + 70 + 15 + 50 = 200
rk10

题解

A. 稻田灌溉(类括号匹配dp)

题目

在这里插入图片描述

分析:

这题是艾教编的,感觉很强。

实际上就是让你对 m m m 个区间的排列计数,满足每个位置被覆盖 [ a i , b i ] [a_i, b_i] [ai,bi] 次。两个区间排列不同当且仅当至少有一个位置上的区间不同。

正常的 d p dp dp 状态好像无法转移,比如 d p i , j dp_{i, j} dpi,j 表示前 i i i 个位置,用了 j j j 个区间且使前 i i i 个位置合法的方案数。那么由于我们不知道每个位置的数具体是多少,因此当枚举新的区间转移时就无法判断能不能转移到一个合法状态上。

如果我们将区间修改看作差分后一个点 + 1 +1 +1,后面的一个点 − 1 -1 1。那么对于一个位置而言覆盖它的区间数显然等于 当前还没有右端点匹配的左端点数。如果我们 d p dp dp 状态中记录了一维表示当前还没有匹配的左端点数,那么每次相当于只用一段区间的状态进行转移,实际上相当于每次直转移合法的状态。并且由于 d p dp dp 往后的顺序,新开左端点也一定在已经考虑过的位置的右边,这样是没有后效性的。

上面这个东西也与 括号匹配 类似, + 1 +1 +1 相当于放一个 ( ( ( − 1 -1 1 相当于放一个 ) ) )。因此我把它叫做 类括号匹配 d p dp dp。感觉遇到 操作需要区间修改,并且需要保证每个位置的状态 时可以考虑这样的思考方式。

那么状态已经很明晰了: d p i , j , k dp_{i, j, k} dpi,j,k 表示前 i i i 个位置,还有 j j j 个左端点没有被匹配,有 k k k 个端点匹配过了的方案数。那么当前位置往下一个位置转移就是枚举下一个位置放多少个左端点 l l l,下一个位置放多少个右端点 h h h,能转移的条件是 a i + 1 ≤ h + l ≤ b i + 1 a_{i + 1} \leq h + l \leq b_{i + 1} ai+1h+lbi+1。然后对应匹配的方案是要乘一个组合数。 时间复杂度 O ( n × m 4 ) O(n \times m^4) O(n×m4)

这里具体说一下组合数是什么:因为 每一天是不同的,所以新开 l l l 个左端点显然需要一个系数 C m − j − k l C_{m -j-k}^{l} Cmjkl,表示 从剩下的几天的左端点中挑 l l l。那么这样所有的 当前左端点都可以看作不同的,因此 h h h 个右端点匹配时可以乘上 C j + l h C_{j+l}^{h} Cj+lh 表示从左端点中挑 h h h 个。

有一个错误的想法是:反正我要求合法的 区间排列数,因此我在新开左端点时不乘系数,新开右端点时乘一个组合数表示不同的区间左右端点的匹配方式,然后乘一个排列数表示把这这 m m m 个区间放在不同的位置。这样肯定是会重的,新开右端点时乘的组合数可能会导致相同的匹配方式被算两次。并且由于会有多个区间的左右端点都相同,因此乘排列数会导致两个区间排列相同。

正解是左右端点没必要同时枚举,可以分开转移。开一个中间数组 f f f 用来先转移左括号,然后转移右括号将 f f f 数组转给 d p dp dp 数组。

时间复杂度 O ( n × m 3 ) O(n \times m^3) O(n×m3)。艾教说循环上界 3 × 1 0 8 3 \times 10^8 3×108,但是我不会卡。照着题解卡过了。

CODE:

#include<bits/stdc++.h>
using namespace std; // dp[i][j][k] 表示前 i 天, 还有j个左端点, 已经匹配了 k 个方案数 
const int N = 105; // f[i][j][k] 表示前 i 天,还有 j 个左端点(当前这一天的左端点钦定过了,右端点还没钦定),已经匹配了 k 个的方案数 
const int M = 305;
typedef long long LL;
const LL mod = 998244353;
LL f[N][M][M], dp[N][M][M], c[M][M]; 
int n, m, a[N], b[N];
int main() {
	for(int i = 0; i < M; i ++ ) {
		for(int j = 0; j <= i; j ++ ) {
			if(j == 0) c[i][j] = 1LL;
			else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
		}
	}
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
	for(int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
	dp[0][0][0] = 1LL;
	LL cnt = 0;
	for(int i = 0; i < n; i ++ ) {
		for(int j = 0; j <= b[i + 1]; j ++ ) {
			for(int k = 0; k + j <= m && m - k >= a[i + 1]; k ++ ) {
				if(dp[i][j][k]) {	
					for(int l = max(0, a[i + 1] - j); l + j + k <= m && l <= b[i + 1] - j; l ++ ) { // i + 1 的左端点 
						   f[i + 1][j + l][k] = (f[i + 1][j + l][k] + dp[i][j][k] * c[m - j - k][l]) % mod;
					}
				}
			}
		}
		for(int j = a[i + 1]; j <= b[i + 1]; j ++ ) {
			for(int k = 0; k + j <= m; k ++ ) {
				if(f[i + 1][j][k]) {
					for(int l = 0; l <= j; l ++ ) { // i + 1 的右端点 
						dp[i + 1][j - l][k + l] = (dp[i + 1][j - l][k + l] + f[i + 1][j][k] * c[j][l]) % mod;
					}
				}
			}
		}
	}
	printf("%lld\n", dp[n][0][m]);
	return 0;
}

B. 最长模区间(baka’s trick 优化双指针)

这题的加强版

在这里插入图片描述

分析:

题意是让我们求一段最长的区间 [ l , r ] [l, r] [l,r],满足区间内所有数 a i a_i ai 对同一个数 m m m 取余后得到的余数相等。

考虑到两个数 a i a_i ai a j a_j aj m m m 取模后相等,那么它们的差 b = ∣ a i − a j ∣ b = |a_i - a_j| b=aiaj 一定是 m m m 的倍数。那么求出差分数组 b b b 后我们就要求出最长的一段 b [ l , r ] b_{[l, r]} b[l,r],满足 [ l , r ] [l, r] [l,r] 区间的 g c d gcd gcd 大于 1 1 1,答案就是 r − l + 1 + 1 r - l + 1 + 1 rl+1+1。由于 当右端点固定时,区间长度越长,区间 g c d gcd gcd 越小。因此我们可以双指针。从小到大枚举右端点 r r r,然后合法的最靠左的 l l l 只会往右移。求区间 g c d gcd gcd 可以预处理一个 s t st st 表。这样建表的复杂度为 O ( n × l o g 2 n × l o g 2 a i ) O(n \times log_2n \times log_2a_i) O(n×log2n×log2ai)。双指针求答案那一部分的复杂度是 O ( n × l o g 2 a i ) O(n \times log_2a_i) O(n×log2ai)。总复杂度是 O ( n × l o g 2 n × l o g 2 a i ) O(n \times log_2n \times log_2a_i) O(n×log2n×log2ai),能够拿 70 p t s 70pts 70pts

正解是一个双指针技巧:baka’s trick。这个技巧可以 在添加元素时函数值容易计算,删去元素时函数值不好算 的双指针中降低复杂度。

比如这道题,在一段区间后接上一个数,新区间的 g c d gcd gcd 好算。只需要原来的 g c d gcd gcd 和当前数 x x x g c d gcd gcd 就好了。但是如果把原来区间左端点右移,那么删去一个数后新区间的 g c d gcd gcd 就没法直接继承。因此我们需要 线段树 或者 st表 维护,这样就多了一个 l o g log log。但是 baka’s trick 能够剪掉这个 l o g log log

具体的想法:我们考虑维护三个指针 l l l r r r m i d mid mid,表示当前当前区间的左端点,右端点,以及中间位点 m i d mid mid。然后维护一个数组 r e s l i resl_i resli 记录从 i i i 位置到 m i d mid mid 位置的函数值。其中 i ∈ [ l , m i d ] i \in [l, mid] i[l,mid]。再记一个变量 r e s r resr resr 表示 m i d mid mid r r r 的函数值。

  • 对于右端点右移,我们让 r + 1 r + 1 r+1,然后更新 r e s r resr resr
  • r e s l l resl_l resll r e s r resr resr 合并算出当前区间的函数值。如果不合法,让 l + 1 l + 1 l+1
  • 如果 l > m i d l > mid l>mid m i d = r , r e s r = W ( r , r ) mid = r,resr = W(r, r) mid=rresr=W(r,r),然后把左指针 l l l 移到 r + 1 r + 1 r+1 并往左跳重构 r e s l resl resl 数组,直到 [ l − 1 , r ] [l - 1, r] [l1,r] 区间的函数值不合法停止。

这样 l l l 是一定不会跳到 m i d mid mid 左边的位置,对于两个指针 l l l r r r,发现它们都跳了 n n n 次,因此复杂度 O ( n ) O(n) O(n)。对于这道题,函数值就是区间 g c d gcd gcd,计算和合并函数值是 l o g 2 a i log_2 a_i log2ai 的复杂度,因此总复杂度就是 O ( n × l o g 2 a i ) O(n \times log_2 a_i) O(n×log2ai)

CODE:

// Baka's trick: 可(n)处理 添加元素计算函数值容易但是删除元素计算函数值困难的双指针 
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
int n, res;
LL a[N], b[N], gd[N];
LL Gcd(LL x, LL y) {return y == 0 ? x : Gcd(y, x % y);}
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
	for(int i = 2; i <= n; i ++ ) b[i] = abs(a[i] - a[i - 1]);
	res = 1;	
	int l = 2, mid = 2, r = 2;
	gd[2] = b[2]; LL resr = b[2];
	for(int i = 2; i <= n; i ++ ) {
		if(r < i) r ++, resr = Gcd(resr, b[r]);
		while(l <= mid && Gcd(gd[l], resr) <= 1) l ++;
		if(l <= mid) res = max(res, r - l + 1 + 1);
		else {
			LL resl = b[r];
			mid = r; l = r + 1;
			resr = b[r];
			while(l > 2 && Gcd(resl, resr) > 1) {
				l --; 
				gd[l] = resl;
				resl = Gcd(resl, b[l - 1]);
			}
			res = max(res, r - l + 1 + 1);
		}
	}
	printf("%d\n", res);
	return 0;
}

C. 三只小猪和狼(组合数学,高斯消元)

这题的加强版

在这里插入图片描述

分析:

实际上给定 n , t n,t nt q q q 次询问,每次询问给出一个数 x ( 1 ≤ x ≤ n t ) x(1 \leq x \leq nt) x(1xnt),求 ∑ i = 1 n C i × t x \sum_{i = 1}^{n} C_{i \times t}^x i=1nCi×tx。如果没有组合意义那么组合数为 0 0 0

数据规模: n ≤ 1 0 6 , 3 ≤ t ≤ 10 , q ≤ 1 0 5 n \leq 10^6,3 \leq t \leq 10,q \leq 10^5 n1063t10q105

考虑如果每次询问暴力计算,那么时间复杂度是 O ( n q ) O(nq) O(nq) 的,有 20 20 20 分。注意到 x x x 的上界是 n t nt nt,我们思考能否提前算出所有 x x x 的答案,然后每次询问 O ( 1 ) O(1) O(1) 输出。

发现询问的式子就是 ∑ i = 1 n t C i x \sum_{i = 1}^{nt} C_i^x i=1ntCix 每次跳 t t t 项求和。我们知道 ∑ i = 1 n t C i x = ∑ i = 0 n t C i x = C n t + 1 x + 1 \sum_{i = 1}^{nt}C_i^x = \sum_{i = 0}^{nt}C_i^x = C_{nt + 1}^{x + 1} i=1ntCix=i=0ntCix=Cnt+1x+1。这个组合恒等式可以这样理解:现在你有 n t + 1 nt + 1 nt+1 个数字,你要选 x + 1 x + 1 x+1 个,考虑枚举最大的那个是什么,剩下的 x x x 个就在比它小的数字里选,那么答案就是 ∑ i = 0 n t C i x \sum_{i = 0}^{nt}C_i^x i=0ntCix。我们 想把答案的求解与这个恒等式联系起来,就要考虑组合数下标不是 t t t 的倍数的项

f j , x = ∑ i = 1 n C i × t + j x ( j ∈ [ 0 , t − 1 ] ) f_{j, x} = \sum_{i = 1}^{n} C_{i \times t + j}^{x}( j \in [0, t - 1]) fj,x=i=1nCi×t+jx(j[0,t1]),那么 x x x 的答案就是 f 0 , x f_{0, x} f0,x

不难发现:
∑ j = 0 t − 1 f j , x = ∑ k = t ( n + 1 ) × t − 1 C k x = ∑ k = 0 ( n + 1 ) × t − 1 C k x − ∑ k = 0 t − 1 C k x = C ( n + 1 ) × t x + 1 − ∑ k = 0 t − 1 C k x \sum_{j = 0}^{t - 1} f_{j, x} = \sum_{k = t}^{(n + 1) \times t - 1} C_{k}^x = \sum_{k = 0}^{(n + 1) \times t - 1}C_{k}^{x} - \sum_{k = 0}^{t - 1}C_{k}^{x} = C_{(n + 1) \times t}^{x + 1} - \sum_{k = 0}^{t - 1}C_{k}^{x} j=0t1fj,x=k=t(n+1)×t1Ckx=k=0(n+1)×t1Ckxk=0t1Ckx=C(n+1)×tx+1k=0t1Ckx。(①)

左边的式子可以 O ( 1 ) O(1) O(1) 求出,右边的式子可以 O ( t ) O(t) O(t) 求出,都很小。

然后还有一些关系:

f j , x = f j − 1 , x + f j − 1 , x − 1 ( j ∈ [ 1 , t − 1 ] ) f_{j,x} = f_{j - 1, x} + f_{j - 1, x - 1}(j \in [1, t - 1]) fj,x=fj1,x+fj1,x1(j[1,t1])。(②)

这个式子的推导也是简单的,只需用到 C n m = C n − 1 m − 1 + C n − 1 m C_{n}^{m} = C_{n - 1}^{m - 1} +C_{n - 1}^{m} Cnm=Cn1m1+Cn1m

联立①②,我们就有了 t t t 个方程,假设我们已经知道了 f p , x − 1 ( p ∈ [ 0 , t − 1 ] ) f_{p, x - 1}(p \in [0, t - 1]) fp,x1(p[0,t1]),那么只有 f 0 , x , f 1 , x , . . . , f t − 1 , x f_{0, x},f_{1, x},...,f_{t - 1, x} f0,xf1,x...ft1,x t t t 个未知数,可以高斯消元解出来(我不会 )。对于一个数 x x x,时间复杂度 O ( t 3 ) O(t^3) O(t3),总时间复杂度是 O ( n t × t 3 ) O(nt \times t^3) O(nt×t3),对于 t = 3 t = 3 t=3 好像够用了。初始就是 f 0 ∼ t − 1 , 0 f_{0 \sim t - 1, 0} f0t1,0 我们都知道,都是 n n n

正解就是把上面的式子进一步回代,我们发现 f j , x f_{j, x} fj,x 可以用 f j − 1 , x f_{j - 1, x} fj1,x 加一个常数表示,进一步也可以把 f j + 1 , x f_{j + 1, x} fj+1,x f j − 1 , x f_{j - 1, x} fj1,x 表示。最后所有 f j , x f_{j, x} fj,x 都可以用 f 0 , x f_{0, x} f0,x 表示(这个可以在纸上把结果化简出来),带到 ① 式子里就可以 O ( 1 ) O(1) O(1) 求出 f 0 , x f_{0, x} f0,x。接着递推能够求出其它的 f j , x f_{j, x} fj,x。时间复杂度 O ( n t × t ) O(nt \times t) O(nt×t)

CODE:

// 看似是递推式,但是初始值不好搞
// 但是还有一个求和的方程,可以高斯消元
// 把式子带入可以得到一个直接出答案的方程,然后就能把初始值解出来,就可以递推了 
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7 + 50;
const int M = 15;
const LL mod = 1e9 + 7;
LL f[N], inv[N], fac[N];
LL dp[2][M], _t; // 0 -> 上一层  1 -> 当前层 
int n, q, t, x;
inline LL Pow(LL x, LL y) {
	LL res = 1LL, k = x % mod;
	while(y) {
		if(y & 1) res = (res * k) % mod;
		y >>= 1;
		k = (k * k) % mod;
	}
	return res;
}
inline LL C(int n, int m) {
	if(n < m) return 0;
	else return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
	scanf("%d%d%d", &n, &t, &q);
	fac[0] = 1LL; _t = Pow(1LL * t, mod - 2LL) % mod;
    for(int i = 1; i < N; i ++ ) fac[i] = (fac[i - 1] * 1LL * i) % mod;
	inv[N - 1] = Pow(fac[N - 1], mod - 2LL) % mod;
	for(int i = N - 2; i >= 0; i -- ) inv[i] = inv[i + 1] * (1LL * (i + 1LL)) % mod;
	for(int i = 0; i <= n * t; i ++ ) {
		if(i == 0) {
			for(int j = 0; j < t; j ++ ) 
			    dp[0][j] = 1LL * n % mod;
		}
		else {
			LL c = C((n + 1) * t, i + 1);
			LL sum = 0;
			for(int j = 0; j < t; j ++ ) sum = (sum + C(j, i)) % mod;
			c = ((c - sum) % mod + mod) % mod;
			LL S = 0;
			for(int j = 0; j <= t - 2; j ++ ) S = (S + 1LL * (t - 1 - j) * dp[0][j] % mod) % mod;
			c = ((c - S) % mod + mod) % mod; 
			dp[1][0] = c * _t % mod;
			for(int j = 1; j <= t - 1; j ++ ) dp[1][j] = (dp[0][j - 1] + dp[1][j - 1]) % mod;
			for(int j = 0; j <= t - 1; j ++ ) dp[0][j] = dp[1][j], dp[1][j] = 0;
		}
		f[i] = dp[0][0];
	}
	while(q -- ) {
		scanf("%d", &x);
		printf("%lld\n", f[x]);
	}
	return 0;
}

D. 黑色连通块

原题链接

在这里插入图片描述

分析:
题面上把题意说的很清楚了,就不再赘述。

50 p t s 50pts 50pts 应该是白送的,不在赘述。

正解:
我们考虑对于每一个黑色连通块找到一个 代表元,那么 代表元 的数量就是黑色连通块的数量。

定义 代表元 为一个黑色连通块中 a i + b j a_i + b_j ai+bj 最小的位置,如果有多个相同的取 i i i 较小的, 如果还有多个相同的 取 j j j 较小的。

考虑一个位置在什么条件下能够成为代表元:
对于一个位置 ( i , j ) (i, j) (i,j),记 u i u_i ui 表示上边第一个 a u i ≤ a i a_{u_i} \leq a_i auiai 的行, d i d_i di 表示下边第一个 a d i < a i a_{d_i} < a_i adi<ai 的行, l j l_j lj 表示左边第一个 b l j ≤ b j b_{l_j} \leq b_j bljbj 的列, r j r_j rj 表示右边第一个 b r j < b j b_{r_j} < b_j brj<bj 的列。
如下图:

在这里插入图片描述

那么 ( i , j ) (i, j) (i,j) 是一个代表元的 充要条件 a i + b j ≤ x a_i + b_j \leq x ai+bjx ( i , j ) (i, j) (i,j) 无法到达这四个点的任意一个点。下面证明这个结论:
充分性:
( i , j ) (i, j) (i,j) 是一个代表元,因此 a i + b j ≤ x a_i + b_j \leq x ai+bjx。如果 ( i , j ) (i, j) (i,j) 能够到达这四个位置之一,那么它一定不是所在连通块的代表元(根据代表元的定义),这与假设矛盾。因此充分性得证。
必要性:
在这里插入图片描述

如果 a i + b j ≤ x a_i + b_j \leq x ai+bjx,那么它一定在一个黑色联通块中。

  • 如果这个黑色连通块在红色边框内(不含边界),那么 ( i , j ) (i, j) (i,j) 这个位置一定是代表元。因为每个位置所在的行 x x x 都满足在 i i i 上面的有 a x > a i a_x > a_i ax>ai,下面的有 a x ≥ a i a_x \geq a_i axai。左右同理。因此任意一个位置 ( x , y ) (x, y) (x,y) 都满足 a x + b y ≥ a i + b j a_x + b_y \geq a_i + b_j ax+byai+bj,如果相等就一定在 ( i , j ) (i, j) (i,j) 的右下方。
  • 如果 ( i , j ) (i, j) (i,j) 能通过一条路径走出边框呢,如存在上图中的一条蓝色路径。那么我们发现蓝色路径在竖直方向上的一条投影路径,即绿色路径上的每个位置也一定是黑色的。那么 ( i , j ) (i, j) (i,j) 至少能到达上下左右四个位置之一,与假设矛盾。因此必要性得证。

证完了上边的结论就简单了。我们考虑对于每个 a i a_i ai 处理出左边第一个小于等于的位置 u i u_i ui,右边第一个小于它的位置 d i d_i di l j l_j lj r j r_j rj 同理,这个可以用 单调栈 解决。然后记 m x a i = m i n ( m a x j = u i i − 1 a j , m a x j = i + 1 d i ) mxa_i = min(max_{j = u_i}^{i - 1}a_j, max_{j = i + 1}^{d_i}) mxai=min(maxj=uii1aj,maxj=i+1di) m x b j mxb_j mxbj 同理。那么 ( i , j ) (i, j) (i,j) 是一个代表元可以表述成下列式子:

  • a i + b j ≤ x a_i + b_j \leq x ai+bjx
  • m x a i + b j > x mxa_i + b_j > x mxai+bj>x
  • m x b j + a i > x mxb_j + a_i > x mxbj+ai>x

固定 i i i 后需要满足:

  • b j ≤ x − a i b_j \leq x - a_i bjxai
  • b j > x − m x a i b_j > x - mxa_i bj>xmxai
  • m x b j > x − a i mxb_j > x - a_i mxbj>xai

这是一个二维偏序问题。
a i a_i ai 按照 x − a i x - a_i xai 从大到小排序,将 b j b_j bj 按照 m x b j mxb_j mxbj 从大到小排序,然后双指针将 b j b_j bj 插入树状数组中,查 ( x − m x a i , x − a i ] (x - mxa_i, x - a_i] (xmxai,xai] 的一段即可。

CODE:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 1e7;
const int N = 2e5 + 10;
LL res;
int n, m, x, num[2][N];
int L[2][N], R[2][N], mx[2][N][21];
int Maxn[2][N];
void build_st(int t, int len) {
	for(int i = 1; i <= len; i ++ ) mx[t][i][0] = num[t][i];
	for(int i = 1; (1 << i) <= len; i ++ ) {
		for(int j = 1; j + (1 << i) - 1 <= len; j ++ ) {
			mx[t][j][i] = max(mx[t][j][i - 1], mx[t][j + (1 << (i - 1))][i - 1]);
		}
	}
}
int query(int t, int l, int r) {
	int k = log2(r - l + 1);
	return max(mx[t][l][k], mx[t][r - (1 << k) + 1][k]);
}
void Get(int t, int len) {
	stack< int > s1, s2;
	for(int i = 1; i <= len; i ++ ) L[t][i] = 0, R[t][i] = len + 1;
	for(int i = 1; i <= len; i ++ ) {
		while(!s1.empty() && num[t][i] < num[t][s1.top()]) {
			int x = s1.top(); R[t][x] = i;
			s1.pop();
		}
		s1.push(i);
	}
	for(int i = len; i >= 1; i -- ) {
		while(!s2.empty() && num[t][i] <= num[t][s2.top()]) {
			int x = s2.top(); L[t][x] = i;
			s2.pop();
		}
		s2.push(i);
	}
	for(int i = 1; i <= len; i ++ ) {
		int c1 = (L[t][i] == 0 ? INF : query(t, L[t][i], i - 1));
		int c2 = (R[t][i] == len + 1 ? INF : query(t, i + 1, R[t][i]));
 		Maxn[t][i] = min(c1, c2);
	}
}
struct element {
	int Mx, v;
}o[N], p[N];
struct BIT {
	int c[N];
	int lowbit(int x) {return x & -x;}
	void add(int x, int y) {for(; x < N; x += lowbit(x)) c[x] += y;}
	int ask(int x) {
		int res = 0;
		for(; x; x -= lowbit(x)) res += c[x];
		return res;
	}
}T;
bool cmpA(element x, element y) {
	return x.v < y.v;
}
bool cmpB(element x, element y) {
	return x.Mx > y.Mx;
}
int main() {
	scanf("%d%d%d", &n, &m, &x);
	for(int i = 1; i <= n; i ++ ) {
        scanf("%d", &num[0][i]);
	}
	for(int i = 1; i <= m; i ++ ) {
		scanf("%d", &num[1][i]);
	}
	build_st(0, n); build_st(1, m);
	Get(0, n); Get(1, m);
	for(int i = 1; i <= n; i ++ ) {
		o[i] = (element) {Maxn[0][i], num[0][i]};
	}
	for(int i = 1; i <= m; i ++ ) {
		p[i] = (element) {Maxn[1][i], num[1][i]};
	}
	sort(o + 1, o + n + 1, cmpA);
	sort(p + 1, p + m + 1, cmpB);
	int j = 1;
	for(int i = 1; i <= n; i ++ ) {
		while(j <= m && p[j].Mx > x - o[i].v) T.add(p[j].v, 1), j ++ ;
		if(x - o[i].v >= 0 && x - o[i].v > x - o[i].Mx) {
			res += 1LL * (T.ask(x - o[i].v) - T.ask(max(0, x - o[i].Mx)));
		}
	}
	printf("%lld\n", res);
	return 0;
}
  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值