代码源每日一题Div.1 (401-407)

401 - 蜗蜗的数列

题目链接
原题:CF1634F

我们设 c [ i ] = a [ i ] − b [ i ] c[i]=a[i]-b[i] c[i]=a[i]b[i]。当对于所有 1 ≤ i ≤ n , c [ i ] = 0 1\leq i\leq n, c[i] = 0 1in,c[i]=0均成立时,两数列相等。但是这样还是要每次操作更新 r − l + 1 r-l+1 rl+1个位置,还是会超时。

斐波那契数列的性质是:对于所有 i > 2 i>2 i>2,均满足 f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i]=f[i-1]+f[i-2] f[i]=f[i1]+f[i2]。我们尝试根据这个性质,通过差分的思想来构造一个新的数列——

我们设 d [ i ] d[i] d[i]为这个新构造的数列,其中 d [ 1 ] = c [ 1 ] , d [ 2 ] = c [ 2 ] − c [ 1 ] d[1]=c[1],d[2]=c[2]-c[1] d[1]=c[1],d[2]=c[2]c[1],对于 i > 2 i>2 i>2 d [ i ] = c [ i ] − c [ i − 1 ] − c [ i − 2 ] d[i]=c[i]-c[i-1]-c[i-2] d[i]=c[i]c[i1]c[i2]。之所以这样构造,是因为这样可以充分利用斐波那契数列的性质,实现差分的思想,每次操作只需要修正有限个数。

先来看如何通过这个构造的数列判断 A B AB AB两数列相等:

d [ 1 ] = 0 d[1]=0 d[1]=0时, c [ 1 ] = 0 c[1]=0 c[1]=0
d [ 1 ] = d [ 2 ] = 0 d[1]=d[2]=0 d[1]=d[2]=0时, c [ 2 ] = 0 c[2]=0 c[2]=0
d [ 3 ] = c [ 3 ] − c [ 2 ] − c [ 1 ] = 0 d[3]=c[3]-c[2]-c[1]=0 d[3]=c[3]c[2]c[1]=0时,可以推出 c [ 3 ] = 0 c[3]=0 c[3]=0
以此类推我们可以发现,当对于所有 1 ≤ i ≤ n 1\leq i\leq n 1in d [ i ] = 0 d[i]=0 d[i]=0均成立时, A B AB AB两数列相等。

现预处理出前 n n n项模 M M M意义下的斐波那契数列 f [ i ] f[i] f[i]

每次操作中,以修正 A A A操作为例,设该操作覆盖范围 [ L , R ] [L,R] [L,R]

我们有:
C L → C L + f [ 1 ] C_L\rightarrow C_L+f[1] CLCL+f[1]
C L + 1 → C L + 1 + f [ 2 ] C_{L+1}\rightarrow C_{L+1}+f[2] CL+1CL+1+f[2]
C L + 2 → C L + 2 + f [ 3 ] C_{L+2}\rightarrow C_{L+2}+f[3] CL+2CL+2+f[3]
⋯ \cdots
C R − 2 → C R − 2 + f [ R − L − 1 ] C_{R-2}\rightarrow C_{R-2}+f[R-L-1] CR2CR2+f[RL1]
C R − 1 → C R − 1 + f [ R − L ] C_{R-1}\rightarrow C_{R-1}+f[R-L] CR1CR1+f[RL]
C R → C R + f [ R − L + 1 ] C_R \rightarrow C_R+f[R-L+1] CRCR+f[RL+1]

D L = C L − C L − 1 − C L − 2 D_L=C_L-C_{L-1}-C_{L-2} DL=CLCL1CL2,只有第一项增加了 f [ 1 ] = 1 f[1]=1 f[1]=1,所以 D L → D L + 1 D_L\rightarrow D_L+1 DLDL+1
D L + 1 = C L + 1 − C L − C L − 1 D_{L+1}=C_{L+1}-C_L-C_{L-1} DL+1=CL+1CLCL1,第一项增加了 f [ 2 ] = 1 f[2]=1 f[2]=1,第二项符号为负,减少了 f [ 1 ] = 1 f[1]=1 f[1]=1,抵消掉了,所以 D L + 1 → D L + 1 D_{L+1}\rightarrow D_{L+1} DL+1DL+1
D L + 2 = C L + 2 − C L + 1 − C L D_{L+2}=C_{L+2}-C_{L+1}-C_L DL+2=CL+2CL+1CL,第一项增加了 f [ 3 ] f[3] f[3],第二项、第三项分别减少 f [ 2 ] , f [ 1 ] f[2],f[1] f[2],f[1],由于有 f [ 3 ] = f [ 2 ] + f [ 1 ] f[3]=f[2]+f[1] f[3]=f[2]+f[1],所以相互抵消, D L + 2 → D L + 2 D_{L+2}\rightarrow D_{L+2} DL+2DL+2
⋯ \cdots
(中间所有项均通过 f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i]=f[i-1]+f[i-2] f[i]=f[i1]+f[i2]可得变化量为 0 0 0
⋯ \cdots
D R + 1 = C R + 1 − C R − C R − 1 D_{R+1}=C_{R+1}-C_R-C_{R-1} DR+1=CR+1CRCR1,第二项减少 f [ R − L ] f[R-L] f[RL],第三项减少 f [ R − L + 1 ] f[R-L+1] f[RL+1],第一项不改变,所以减少的是 f [ R − L ] + f [ R − L + 1 ] = f [ R − L + 2 ] f[R-L]+f[R-L+1]=f[R-L+2] f[RL]+f[RL+1]=f[RL+2],即 D R + 1 → D R + 1 − f [ R − L + 2 ] D_{R+1}\rightarrow D_{R+1}-f[R-L+2] DR+1DR+1f[RL+2]
D R + 2 = C R + 2 − C R + 1 − C R D_{R+2}=C_{R+2}-C_{R+1}-C_R DR+2=CR+2CR+1CR,只有第一项减少 f [ R − L + 1 ] f[R-L+1] f[RL+1],即 D R + 1 → D R + 1 − f [ R − L + 1 ] D_{R+1}\rightarrow D_{R+1}-f[R-L+1] DR+1DR+1f[RL+1]

所以这种构造方法,每进行一次操作,只需要修正四个位置的 d [ i ] d[i] d[i]即可。

对于 B B B数组的操作,只需要将 4 4 4个位置的变化量变为相反数即可。

每次操作结束后,如果对于所有 1 ≤ i ≤ n 1\leq i\leq n 1in d [ i ] = 0 d[i]=0 d[i]=0均成立,则输出Yes。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1000005;

LL n, q, mod, l, r, z;
LL a[N], b[N], c[N], d[N], f[N];
char x;

void add(int i, int x) {
	if (i > n) return;
	if (d[i] == 0) --z;
	d[i] += x;
	d[i] = (d[i] % mod + mod) % mod;
	if (d[i] == 0) ++z; 
}

void main2() {
	cin >> n >> q >> mod;
	z = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= n; ++i) {
		cin >> b[i];
		c[i] = a[i] - b[i];
	}
	d[1] = (c[1] % mod + mod) % mod; 
	d[2] = ((c[2] - c[1]) % mod + mod) % mod;
	if (d[1] == 0) ++z;
	if (d[2] == 0) ++z;
	for (int i = 3; i <= n; ++i) {
		d[i] = c[i] - c[i - 1] - c[i - 2];
		d[i] = (d[i] % mod + mod) % mod;
		if (d[i] == 0) ++z;
	}
	f[1] = f[2] = 1;
	for (int i = 3; i <= n; ++i) {
		f[i] = (f[i - 1] + f[i - 2]) % mod;
	}
	for (int i = 1; i <= q; ++i) {
		cin >> x >> l >> r;
		if (x == 'A') {
			add(l, 1);
			add(r + 1, -f[r - l + 2]);
			add(r + 2, -f[r - l + 1]);
		}
		else {
			add(l, -1);
			add(r + 1, f[r - l + 2]);
			add(r + 2, f[r - l + 1]);
		}
		if (z == n) cout << "Yes\n";
		else cout << "No\n";
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

402 - 最大公约数

题目链接

如果选择的几段的最大公约数的答案是 g g g,那么选择的所有段的内部元素和都是 g g g的倍数。所以我们可以认为,整个数列的和也是 g g g的倍数。反过来可以发现,所有的答案,只可能是整个数列的和的因子。

当我们分的段数越多,就意味着我们得到的最大公约数的答案越小。因为,假如说我分成 x x x段的答案是 g g g,那么将这 x x x个段合并成更少的段,答案一定也可以是 g g g。所以段越少,可能的答案就越大。

枚举所有的因数 x x x

我们要找序列中所有和为 x x x的倍数的段,设满足条件的段的左端点为 L L L,右端点为 R R R。如果序列的前缀和为 p r e [ i ] pre[i] pre[i],那么一定有 p r e [ L ]   m o d   x = p r e [ R ]   m o d   x pre[L]\bmod x = pre[R]\bmod x pre[L]modx=pre[R]modx。换句话说,对于所有可能的 p r e [ i ]   m o d   x = a pre[i]\bmod x=a pre[i]modx=a,统计这样的 i i i的数量。找出最大的那个数量,设其为 t t t。由于序列是一个环,所以有 t t t个分段点,就有 t t t个满足的区间(最外侧靠环连在一起的那个区间也一定满足,因为中间连续的都是 x x x的倍数,所有和也是 x x x的倍数,那么剩下的外侧的数的和也一定是 x x x的倍数)。那么能形成 t t t个区间的值 a n s [ t ] = max ⁡ ( a n s [ t ] , x ) ans[t]=\max(ans[t], x) ans[t]=max(ans[t],x)

根据答案的单调性,分的段数越少,答案就应该越大,所以我们用段数多的 a n s ans ans来更新段数少的 a n s ans ans,让其变成自己与上一个 a n s ans ans之间的最大值。最后输出 a n s ans ans数组即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

LL n, sum;
LL a[2050], pre[2005], ans[2005];
vector<LL> fac;

void main2() {
	cin >> n;
	pre[0] = sum = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		pre[i] = pre[i - 1] + a[i];
		sum += a[i];
	}
	for (LL i = 1; i * i <= sum; ++i) {
		if (sum % i == 0) {
			fac.push_back(i);
			if (i != sum / i) fac.push_back(sum / i);
		}
	} 
	for (LL x: fac) {
		map<LL, LL> mp;
		LL res = 0;
		for (int i = 1; i <= n; ++i) {
			int mod = pre[i] % x;
			++mp[mod];
			res = max(res, mp[mod]); 
		}
		ans[res] = max(ans[res], x);
	}
	for (int i = n - 1; i >= 1; --i) {
		ans[i] = max(ans[i], ans[i + 1]);
	}
	for (int i = 1; i <= n; ++i) {
		cout << ans[i] << '\n';
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

403 - 平方计数

题目链接

根据题意,有 a i 2 + a j = x 2 a_i^2+a_j=x^2 ai2+aj=x2,移项可得 a j = x 2 − a i 2 = ( x − a i ) ( x + a i ) a_j=x^2-a_i^2=(x-a_i)(x+a_i) aj=x2ai2=(xai)(x+ai)。我们发现, x − a i x-a_i xai x + a i x+a_i x+ai a j a_j aj的两个因子,而且这两个因子的差为 2 a i 2a_i 2ai。所以我们可以枚举每一个 a j a_j aj的两个因子,将这两个因子做差取绝对值,看看除以 2 2 2后是否在序列中存在。这种方法的时间复杂度是 O ( n n ) O(n\sqrt n) O(nn )的,在这道题目中会超时。

我们换一种枚举因子的方式,不以枚举 a j a_j aj入手,而是直接用倍数遍历的方法,用 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度进行遍历,遍历每一个 i ( 1 ≤ i ≤ 1 0 6 ) i(1\leq i\leq 10^6) i(1i106),对于每一个 i i i,遍历其所有倍数 j j j,此时 j j j就是 a j a_j aj,两个因子分别就是 i i i j / i j/i j/i。由于我们的 i i i会依次遍历到这两个数,所以相同的情况我们会遇到 2 2 2次,最终情况需要除以 2 2 2。对于每一种情况,先计算两个因子的差的绝对值,如果这个差的绝对值是偶数,那么这个情况对答案的贡献就是差的绝对值在序列中出现的次数和 j j j在序列中出现的次数的乘积。

最后将整体答案除以 2 2 2

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int n;
int a[1000005], mp[1000005];

void main2() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		++mp[a[i]];
	}
	LL ans = 0;
	for (int i = 1; i <= 1000000; ++i) {
		for (int j = i; j <= 1000000; j += i) {
			int d = abs(j / i - i);
			if (d % 2 == 0) {
				d /= 2;
				ans += (mp[d] * mp[j]);
			}
		}
	}
	cout << ans / 2;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

404 - 字典序最小

题目链接

我们期望最后得到的数组是这个样子: [ 1 , 2 , 3 , 4 , ⋯   , n ] [1,2,3,4,\cdots,n] [1,2,3,4,,n]。但是因为给定的数组有顺序,所以我们很有可能达不成我们理想的目标。

我们从左往右遍历给定的序列,然后维护一个栈。如果当前元素没有在栈中,而且栈顶中有一些元素是比这个数大的,如果栈顶的元素在后面还会出现,那么就让那个数后面再进栈,先让小的数进到他的前面来。如果这个数后面没有再出现过了,意味着后面不会有机会再进栈,那么就不弹出,直接将当前的数放到栈顶。

遍历 m m m个数之后,得到的栈的内容就是我们要输出的结果。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1000005;

int m, n, si = 0;
int lst[N], a[N], ins[N], s[N];

void main2() {
	cin >> m >> n;
	for (int i = 1; i <= m; ++i) {
		cin >> a[i];
		lst[a[i]] = i;
	}
	for (int i = 1; i <= m; ++i) {
		if (ins[a[i]]) continue;
		while (si and s[si] > a[i] and lst[s[si]] > i) {
			ins[s[si]] = 0;
			--si;
		}
		s[++si] = a[i];
		ins[a[i]] = 1;
	}
	cout << s[1];
	for (int i = 2; i <= n; ++i) {
		cout << ' ' << s[i];
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

405 - 拆拆

题目链接

很容易想到将 X X X进行质因数分解,我们现在有 Y Y Y个位置,首先,假设我们这 Y Y Y个位置全部放正数,那么结果就是考虑 X X X有多少种分解方法,可以让任意两种方法之间,保证不是每一个位置的数都一样。

质因数分解后,我们考虑合并这些质因数。但是单纯从合并上去考虑的话,计算起来非常复杂。我们可以把合并这个过程抽象成往一个盒子里放小球的过程。因为要防止完全相同的两种方案出现,由于质因数分解出来之后,会存在相同的数和不同的数,不能将所有数看成相同的小球或不同的小球。如果我们每一种质因数单独考虑的话,那么不同种质因数之间是无关的,每一种质因数的放置总数相乘,就是我们所求的问题的答案。

那么对于任意一个分解出来的质因数,我们设分解出来了 a a a个,一共有 Y Y Y个位置,问题就变成了有 a a a个完全相同的小球和 Y Y Y个不同的盒子(区别在位置关系),允许出现空盒子(默认里面有一个球表示正整数 1 1 1),有多少种不同的摆放方式。这个问题的答案是 ( a + Y − 1 Y − 1 ) \binom{a+Y-1}{Y-1} (Y1a+Y1)
一篇介绍得很好的8种球盒问题的博客:Ljnoit - 【C++】球盒问题总结(八种情况)

将所有的质因数的答案乘到一起,就是我们这 Y Y Y个位置全部放正数的答案,设这个答案为 S S S。那么现在要考虑的就只有符号的问题了。我们要保证有偶数个负号,一共有 Y Y Y个位置,所以可以分配负号的方案是 ( Y 0 ) + ( Y 2 ) + ( Y 4 ) + ⋯ = 2 Y − 1 \binom{Y}{0}+\binom{Y}{2}+\binom{Y}{4}+\cdots=2^{Y-1} (0Y)+(2Y)+(4Y)+=2Y1

那么最终答案就是 S × 2 Y − 1 S\times 2^{Y-1} S×2Y1

需要提前预处理所有数的质因数。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;

LL jc[2000050], inv[2000050];

template<class T> T power(T a, LL b) {
	T res = 1;
    for (; b; b >>= 1) {
        if (b % 2) res = (res * a) % mod;
        a = (a * a) % mod;
    }
    return res;
}

void init(LL iN) {
	jc[0] = 1;
	for (LL i = 1; i <= iN + 1; ++i) {
		jc[i] = (jc[i - 1] * i) % mod;
	}
	inv[iN + 1] = power(jc[iN + 1], mod - 2);
	for (LL i = iN; i >= 0; --i) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	} 
}

LL C(LL N, LL M) {
	return jc[N] * inv[M] % mod * inv[N - M] % mod;
}

LL bib(LL n, LL m) {
	return C(n + m - 1, m - 1);
}

LL x, y, ans;
vector<pair<LL, LL> > fac[1000005];

void main2() {
	cin >> x >> y;
	ans = power(2ll, y - 1);
	for (auto [a, b]: fac[x]) {
		ans = (ans * bib(b, y)) % mod;
	}
	cout << ans << '\n';
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
	init(2000000); 
	for (int i = 2; i <= 1000000; i++) {
	    if (fac[i].size()) continue;
	    for (int j = i; j <= 1000000; j += i) {
	        int t = j, cnt = 0;
	        while (t % i == 0) t /= i, cnt++;
	        fac[j].push_back({i, cnt});
	    }
	}
	cin >> _;
//	_ = 1;
	while (_--) main2();
	return 0;
}

406 - “Z”型矩阵

题目链接
参考了严格鸽 - (离线/树状数组)代码源每日一题 Div1“Z”型矩阵

首先维护每一个点向左最远延申的数量 l [ i ] [ j ] l[i][j] l[i][j]和向右最远延申的数量 r [ i ] [ j ] r[i][j] r[i][j]

我们想找"Z"型的矩阵,可以从对角线上入手。我们遍历每一条从右上到左下方向的斜线,考虑以这条线为"Z"的斜线的、边长大于 1 1 1的"Z"型矩阵数量。我们在斜线上选取一段连续的 z z z,从第一个点开始,如果这个点向左可以延伸,那么就按照向左的长度,寻找能够匹配向左延申的这些 z z z的范围,在这个点和后面的 l [ i ] [ j ] − 1 l[i][j]-1 l[i][j]1个点上。也就是说,设这个点是连续 x x x个点中的第 i i i个点,那么我们在遍历完第 i + l [ i ] [ j ] − 1 i+l[i][j]-1 i+l[i][j]1个点后,这个点就不会再产生贡献了。

总结:访问到第 i i i个点时,在这个点设一个标记 1 1 1,并在第 i + l [ i ] [ j ] − 1 i+l[i][j]-1 i+l[i][j]1个点被访问完后,将这个标记删除。

如果这个点可以向右延申,设这个点是连续的 z z z的第 i i i个点,那么可以和这段匹配的是 [ i − r [ i ] [ j ] + 1 , i − 1 ] [i-r[i][j]+1,i-1] [ir[i][j]+1,i1]的点。这上面的点每有一个标记,就可以匹配一次。于是,统计这个区间内的 1 1 1的个数即可。

这个点遍历完后,看看有没有需要在这个点遍历完后,需要在这个时候删除的前面的点的标记。

实现方法:用一个vector: d e l [ i ] del[i] del[i]表示需要在第 i i i个点遍历完时,在此时需要移除的标记所在的点的编号的集合。用树状数组维护标记和求区间和。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3005; 

int n, m;
int a[3005][3005], l[3005][3005], r[3005][3005];

int c[N];

int lowbit(int x) {
	return x & (-x);
}

void add(int x, int k, int lim) {
	while (x <= lim) {
		c[x] += k; x += lowbit(x);
	}
}

LL presum(int x) {
	LL ans = 0;
	while (x >= 1) {
		ans += c[x]; x -= lowbit(x);
	}
	return ans;
}

vector<pair<int, int> > f;
vector<int> del[3005];

LL count(int lim) {
	LL ret = 0;
	int nn = f.size();
	for (int i = 0; i <= nn; ++i) {
		c[i] = 0;
		del[i].clear();
	}
	int cnt = 0;
	for (auto [x, y]: f) {
		++cnt;
		if (r[x][y] > 1) {
			ret += presum(cnt - 1) - presum(cnt - r[x][y]);
		}
		if (l[x][y] > 1) {
			add(cnt, 1, nn);
			del[min(lim, cnt + l[x][y] - 1)].push_back(cnt);
		}
		for (int y: del[cnt]) {
			add(y, -1, nn);
		}
	}
	f.clear();
	return ret;
}

void main2() {
	cin >> n >> m;
	LL ans = 0;
	for (int i = 1; i <= n; ++i) {
		string x; cin >> x;
		for (int j = 0; j < m; ++j) {
			if (x[j] == 'z') a[i][j + 1] = 1, ++ans;
			else a[i][j + 1] = 0;
			l[i][j + 1] = r[i][j + 1] = 0;
		}
	}	
	for (int i = 1; i <= n; ++i) {
		l[i][0] = r[i][m + 1] = 0;
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (a[i][j]) l[i][j] = l[i][j - 1] + 1;
			else l[i][j] = 0;
		}
		for (int j = m; j >= 1; --j) {
			if (a[i][j]) r[i][j] = r[i][j + 1] + 1;
			else r[i][j] = 0;
		}
	}
	for (int j = 1; j <= m; ++j) {
		for (int i = 1; i <= min(n, j); ++i) {
			int x = i, y = j + 1 - i;
			if (!a[x][y]) {
				ans += count(min(n, j));
				continue;
			}
			f.push_back({x, y});
		}
		if (f.size()) ans += count(min(n, j));
	}
	for (int j = 2; j <= n; ++j) {
		for (int i = 1; i <= min(n, n - j + 1); ++i) {
			int x = i + j - 1, y = m - i + 1;
			if (!a[x][y]) {
				ans += count(min(n, n - j + 1));
				continue;
			}
			f.push_back({x, y});
		}
		if (f.size()) ans += count(min(n, n - j + 1));
	}
	cout << ans;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

407 - 好序列

题目链接

可以发现,只要包含唯一存在的数的区间都是合法区间。换句话说,如果发现了一个数在这一段区间内只出现一次,那么我们就无需考虑这个区间,而只需要考虑这个区间被这个数分开之后的两个区间是怎么样的。具体说,就是,假设在 [ L , R ] [L,R] [L,R]区间内有一个位置 x x x,在 [ L , R ] [L,R] [L,R]区间内,这个位置的数只出现过一次,那么 [ L , R ] [L,R] [L,R]肯定是合法区间,只需要考虑 [ L , x − 1 ] [L,x-1] [L,x1] [ x + 1 , R ] [x+1,R] [x+1,R]是否合法即可。

所以我们需要维护两个数组: l s t [ i ] lst[i] lst[i]表示与第 i i i个数相同的、上一个出现的数的位置; n x t [ i ] nxt[i] nxt[i]表示与第 i i i个数相同的、下一个出现的数的位置。如果同时满足 l s t [ i ] < L lst[i] < L lst[i]<L n x t [ i ] > R nxt[i]>R nxt[i]>R,说明 i i i这个位置的数在 [ L , R ] [L,R] [L,R]内是唯一出现的。但是我们这样去分裂区间,会超时。因为整个的时间复杂度是 O ( n 2 ) O(n^2) O(n2)的。

这里要用到的思想就是启发式分裂的思想。我们同时从两边向中间搜索,那么离两端最近的一个唯一出现的位置 x x x,设其离边界的最近距离是 d d d。显然这个 d d d会先被发现,且一定有 d ≤ 1 2 ( R − L + 1 ) d\leq \frac{1}{2} (R-L+1) d21(RL+1)。这样不断分裂的过程,和归并排序是非常相似的,所以我们可以感性地认为,这样的时间复杂度可以从 O ( n 2 ) O(n^2) O(n2)降低至 O ( n log ⁡ n ) O(n\log n) O(nlogn)

如果分裂到最后都没有问题,就输出"not boring";否则输出"boring"。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int n, mi;
int a[200005], lst[200005], nxt[200005], tmp[200005];

bool split(int l, int r) {
	if (l >= r) return true;
	int x = l, y = r;
	while (x <= y) {
		if (lst[x] < l and nxt[x] > r) {
			return (split(l, x - 1) and split(x + 1, r));
		}
		if (lst[y] < l and nxt[y] > r) {
			return (split(l, y - 1) and split(y + 1, r));
		}
		++x; --y;
	}
	return false;
}

void main2() {
	cin >> n;
	map<int, int> mp;
	mi = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		if (!mp[a[i]]) mp[a[i]] = ++mi;
		a[i] = mp[a[i]];
	}
	for (int i = 1; i <= mi; ++i) {
		tmp[i] = 0;
	}
	for (int i = 1; i <= n; ++i) {
		lst[i] = tmp[a[i]];
		tmp[a[i]] = i;
	}
	for (int i = 1; i <= mi; ++i) {
		tmp[i] = n + 1;
	}
	for (int i = n; i >= 1; --i) {
		nxt[i] = tmp[a[i]];
		tmp[a[i]] = i;
	}
	if (split(1, n)) cout << "non-boring\n";
	else cout << "boring\n";
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
	cin >> _;
//	_ = 1;
	while (_--) main2();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值