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

news2025/4/6 17:36:56

文章目录

  • 前言
  • 时间安排及成绩
  • 题解
    • A. 稻田灌溉(类括号匹配dp)
    • B. 最长模区间(baka's trick 优化双指针)
    • C. 三只小猪和狼(组合数学,高斯消元)
    • D. 黑色连通块

前言

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

时间安排及成绩

  • 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1959537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

睿考网:中级会计师和注册会计师哪个难?

中级会计师和注册会计师两个资格证书对比下来&#xff0c;后者具有更高的挑战性&#xff0c;主要原因有以下几点&#xff1a; 1. 考试科目和内容&#xff1a; 注册会计师考试包含专业阶段与综合阶段&#xff0c;共涉及六个科目&#xff0c;考察的内容覆盖范围更广泛&#xff…

最新Yiso智云搜索引擎系统源码/开源PHP源码/修复版

源码简介&#xff1a; 最新Yiso智云搜索引擎系统源码/开源PHP源码/修复版。Yiso 是一个性能非常好的搜索引擎&#xff0c;不仅免费开源&#xff0c;还能当作收录网址的平台来用呢&#xff01;只需要输入关键词&#xff0c;就能轻松找到相关的搜索结果内容。 1、Yiso 用的是自…

脚本: 监控Oracle数据库中正在运行的SQL(Oracle DBA的工作利器)

英文原文网址&#xff1a;[Script: Monitoring Running SQL in Oracle Database in Real Time] (https://byte-way.com/2024/07/24/script-monitoring-running-sql-in-oracle-database-in-real-time/) 以下SQL查询活动会话及其正在执行的SQL语句的信息&#xff0c;并提供有关其…

生成式AI 未来发展的两大要素:数据和开发者

这一年来&#xff0c;生成式 AI 领域的发展可谓日新月异。大语言模型 (LLM) 已经从学术研究圈的新宠&#xff0c;变成了开发者、产品经理、IT 决策者、高管等所有人都密切关注和亲身参与的重要课题。 一年间&#xff0c;这类问题在新闻报道、技术大会、开发者闲聊、同事讨论、…

【Dash】Hello World

一、最简单的 Dash Building and launching an app with Dash can be done with just 5 lines of code. Open a Python IDE on your computer, create an app.py file with the code below and install Dash if you havent done so already. To launch the app, type into yo…

DBeaver如何连接本地的mysql服务

要使用 DBeaver 连接本地的 MySQL 服务&#xff0c;可以按照以下步骤进行设置&#xff1a; 1. 下载和安装 DBeaver 首先确保已经下载并安装了 DBeaver。你可以从官方网站 DBeaver 官网 下载适用于 macOS 的安装包&#xff0c;并按照提示安装。 2. 打开 DBeaver 并添加新的数…

Python编程的思维导图

创建一个Python编程的思维导图是一个很好的方式来组织和理解Python编程的核心概念、语法、库和应用领域。下面是一个简化的Python编程思维导图的概要&#xff0c;可以根据需要进行扩展或修改&#xff1a; Python编程 ├── 基础概念 │ ├── 变量与数据类型 │ │…

基于 YOLO V10 Fine-Tuning 训练自定义的目标检测模型

一、YOLO V10 在本专栏的前面几篇文章中&#xff0c;我们使用 ultralytics 公司开源发布的 YOLO-V8 模型&#xff0c;分别 Fine-Tuning 实验了 目标检测、关键点检测、分类 任务&#xff0c;实验后发现效果都非常的不错&#xff0c;但它已经不是最强的了。最新的 YOLO-V10 已经…

如何看待储殷教授说的“现在的码农和纺织工人没区别“

储殷教授的观点认为现代的程序员&#xff08;通常被称为“码农”&#xff09;与过去的纺织工人没有本质的区别。这种说法引发了一些讨论和争议&#xff0c;码哥从几个角度来探讨这一观点&#xff1a; 工作性质的比较 重复性劳动 储殷教授可能认为&#xff0c;就像过去纺织工人…

18、基于DDD的微服务设计实例

在本章基于DDD的微服务设计实例中&#xff0c;我们将通过一个实际的微服务设计实例&#xff0c;详细介绍如何基于领域驱动设计&#xff08;DDD&#xff09;来构建微服务架构。这个实例不仅涵盖了微服务设计的基本原则&#xff0c;还展示了实际应用中的具体实现细节和最佳实践。…

pypi如何上传自己的代码记录

目录 一. 注册pypi账号并创建token 1. 注册pypi账号并创建token 2. Pypi账号注册 3. 邮箱验证 ​编辑 4. 重新生成恢复代码 5. 输入账号密码 ​编辑 6. 保存code并继续 ​编辑7. 输入一行即可&#xff0c;然后点击verify 8. 点击左方目录内的account setting&#xff…

17K star!30秒偷走你的声音,开源声音克隆工具

现在的AI发展越来越快&#xff0c;生成一段语音不是难事&#xff0c;那如果生成的是你自己的声音&#xff0c;你觉得如何&#xff1f; 今天我们分享一款开源的声音克隆工具&#xff0c;只需30秒的一般音源&#xff0c;他就可以偷走你的声音&#xff0c;它就是&#xff1a;Open…

【Vulnhub系列】Vulnhub_Seattle_003靶场渗透(原创)

【Vulnhub系列靶场】Vulnhub_Seattle_003靶场渗透 原文转载已经过授权 原文链接&#xff1a;Lusen的小窝 - 学无止尽&#xff0c;不进则退 (lusensec.github.io) 一、环境准备 1、从百度网盘下载对应靶机的.ova镜像 2、在VM中选择【打开】该.ova 3、选择存储路径&#xff0…

【扒代码】X = output[:,:,y1:y2,x1:x2].sum()

假设我们有以下输入&#xff1a; output 是一个形状为 (1【batch size】, 1【channel】, 10, 10) 的张量&#xff0c;表示一个 10x10 的输出图像。boxes 是一个形状为 (1【index】, 2, 5) 的张量&#xff0c;表示两个边界框&#xff0c;每个边界框包含 5 个值 [index, y1, x1,…

聊聊 ChatGPT

一、ChatGPT一次添加一个词 ChatGPT作用&#xff1a;人为输入任何文本后&#xff0c;自动生成一个"合理的延续"&#xff0c;合理指的是&#xff1a;假如你看了数十亿网页上的内容后&#xff0c;发现大家都这么写&#xff0c;那你也这么写&#xff0c;就是合理的。Ch…

NC 删除有序链表中重复的元素-I

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 删除给出链表…

代码随想录训练营 Day14打卡 二叉树 part02 226.翻转二叉树 101. 对称二叉树 104. 二叉树的最大深度 111. 二叉树的最小深度

代码随想录训练营 Day14打卡 二叉树 part02 一、 力扣226. 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 &#xff1a; 输入&#xff1a; root [4,2,7,1,3,6,9] 输出&#xff1a; [4,7,2,9,6,3,1] 我们下文以前序遍…

前端工程化-vue项目创建

可以使用html、css、javascpript ,以及使用vue、axios等技术搭建前端页面&#xff0c;但效率低、结构乱。 实际前端开发&#xff1a; 前端工程化开发步骤&#xff1a; 一、环境准备 1.安装NodeJS2. 安装vue-cli 二、创建Vue项目 有两种方式创建&#xff0c;一般采用第二种图…

【连续3年稳定发表,门槛低 易中稿】第四届先进制造技术与电子信息国际学术会议(AMTEI 2024,9月20-22)

由深圳技术大学集成电路与光电芯片学院、中南大学自动化学院联合支持的第四届先进制造技术与电子信息学术会议&#xff08;AMTEI 2024&#xff09;将于2024年09月20-22日在重庆召开。 本次会议主要围绕先进制造技术与电子信息的最新研究领域&#xff0c;为来自国内外高等院校、…

Springboot+Thymeleaf实现纯静态化页面处理

前言&#xff1a;引入包什么的就不讲了&#xff0c;这里我只记录如何实现。 在template目录下构建模板页面 IndexTemplate.html。一般模板文件都是放在这个下面<!DOCTYPE html> <html lang"zh" xmlns:th"http://www.thymeleaf.org"> <head&…