A.Era
题目描述
给一个长为 n n n 的序列 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an,你可以对序列做任意次如下所述的操作:选择一个数字 k k k(每次操作选择的数字可以不同),将其插入到序列 a a a 的任意位置,比如: a = [ 3 , 3 , 4 ] a=[3,3,4] a=[3,3,4], k = 2 k=2 k=2,操作后可能的序列有 [ 2 , 3 , 3 , 4 ] , [ 3 , 2 , 3 , 4 ] , [ 3 , 3 , 2 , 4 ] , [ 3 , 3 , 4 , 2 ] [2,3,3,4],[3,2,3,4],[3,3,2,4],[3,3,4,2] [2,3,3,4],[3,2,3,4],[3,3,2,4],[3,3,4,2]。
操作尽可能少的次数,使序列 a a a 满足: ∀ i , a i ≤ i \forall i,a_i\leq i ∀i,ai≤i,求这个最少的操作次数。
数据范围:多组测试数据, 1 ≤ n ≤ 100 1\leq n\leq 100 1≤n≤100, 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1≤ai≤109。
分析
对于某个下标 i i i,如果 a i > i a_i>i ai>i,最少需要在位置 i i i 前插入 a i − i a_i-i ai−i 个数字。枚举所有的 a i − i a_i-i ai−i,其中的最大值即为答案,即 a n s = max i = 1 n { a i } ans=\max\limits_{i=1}^{n}\{a_i\} ans=i=1maxn{ai}。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++) {
int k;
scanf("%d", &k);
ans = max(ans, k - i);
}
cout << ans << endl;
}
}
B.XOR Specia-LIS-t
题目描述
给一个长为 n n n 的序列 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an,把它分成若干个不相交的子序列,第 i i i 个子序列的最长递增子序列的长度定义为 h i h_i hi。比如我们把序列 [ 2 , 5 , 3 , 1 , 4 , 3 , 2 , 2 , 5 , 1 ] [2,5,3,1,4,3,2,2,5,1] [2,5,3,1,4,3,2,2,5,1] 分割成 [ 2 , 5 , 3 , 1 , 4 ] , [ 3 , 2 , 2 , 5 ] , [ 1 ] [2,5,3,1,4],[3,2,2,5],[1] [2,5,3,1,4],[3,2,2,5],[1],则 h = [ 3 , 2 , 1 ] h=[3,2,1] h=[3,2,1]。
判断能否以某种方式分割 a a a 序列,使得 h h h 序列的异或和等于 0 0 0。
数据范围:多组测试数据, 2 ≤ n ≤ 1 0 5 2\leq n\leq 10^5 2≤n≤105, 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1≤ai≤109, n n n 的总和不超过 3 × 1 0 5 3\times 10^5 3×105。
分析
如果
n
n
n 是偶数,把
a
a
a 序列分割成长为
1
1
1 的
n
n
n 个子序列,答案一定是 YES
。
如果
n
n
n 是奇数,我们考虑寻找满足
a
i
≥
a
i
+
1
a_i\geq a_{i+1}
ai≥ai+1 的相邻的两个数字,把这两个数字作为一个子序列分割出来,最长递增子序列的长度为
1
1
1,如果找的到,答案为 YES
,否则为 NO
。
代码
#include<bits/stdc++.h>
using namespace std;
int a[300010];
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
bool flag = true;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n - 1; i++)
if (a[i] >= a[i + 1]) {
flag = false;
}
if (n % 2 == 0 || flag == false) {
puts("YES");
} else {
puts("NO");
}
}
return 0;
}
C.Di-visible Confusion
题目描述
给一个长为 n n n 的序列 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an, 进行如下操作:选择一个 a i a_i ai,要求 ( i + 1 ) ∤ a i (i+1)\nmid a_i (i+1)∤ai,将其从序列中移除。判断能否通过若干次操作使得序列变为空。
数据范围:多组测试数据, 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1≤n≤105, 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1≤ai≤109, n n n 的总和不超过 3 × 1 0 5 3\times 10^5 3×105。
分析
对于所有的
i
i
i,如果
2
2
2 ~
i
+
1
i+1
i+1 中至少存在一个数字不整除
a
i
a_i
ai,则答案为 YES
,否则答案为 NO
。
使用数学归纳法来证明:如果 a n a_n an 前面的 n − 1 n-1 n−1 个数字都能被移除,则 a n a_n an 也能在其位置为 $1 $ ~ n n n 中的某个位置时被移除(假设该位置为 k k k, a n a_n an 可以在序列中有 k − 1 k-1 k−1 个数字时被移除)。
下面考虑如何快速判断 2 2 2 ~ $ i+1$ 中是否至少有一个数字不整除 a i a_i ai。如果 a i a_i ai 能被 2 2 2 ~ $ i+1$ 的所有数字整除,这代表 a i a_i ai 也能被 LCM ( 2 , 3 , ⋯ , i + 1 ) \text{LCM}(2,3,\cdots,i+1) LCM(2,3,⋯,i+1) 整除,但是当 n = 22 n=22 n=22 时, LCM ( 2 , 3 , ⋯ , 23 ) > 1 0 9 > a i \text{LCM}(2,3,\cdots,23)>10^9>a_i LCM(2,3,⋯,23)>109>ai。所以当 i ≥ 22 i\geq 22 i≥22 时,必然存在一个数字不整除 a i a_i ai,不需要逐一判断;当 i < 22 i<22 i<22 时,暴力检验。时间复杂度 O ( n + 2 1 2 ) O(n+21^2) O(n+212)。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
bool flag = true;
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
bool f = false;
for (int j = i + 1; j >= 2; j--) {
if (x % j != 0) {
f = true;
break;
}
}
flag = flag & f;
}
if (flag == true) {
puts("YES");
} else {
puts("NO");
}
}
return 0;
}
D.Moderate Modular Mode
题目描述
有两个偶数 x , y ( 2 ≤ x , y ≤ 1 0 9 ) x,y(2\leq x,y\leq 10^9) x,y(2≤x,y≤109),找到一个数字 n ( 2 ≤ n ≤ 1 0 18 ) n(2\leq n\leq 10^{18}) n(2≤n≤1018),使得 n m o d x = y m o d n n\mod x=y\mod n nmodx=ymodn。
分析
如果 x > y x>y x>y,则 ( x + y ) m o d x = y m o d x = y m o d ( x + y ) = y (x+y)\mod x=y\mod x=y\mod (x+y)=y (x+y)modx=ymodx=ymod(x+y)=y,即 n = x + y n=x+y n=x+y。
如果 x ≤ y x\leq y x≤y:
结论一: n ≥ x n\geq x n≥x。
证明:使用反证法证明,存在某个 n < x n<x n<x 使得 n m o d x = y m o d n n\mod x=y\mod n nmodx=ymodn 成立。则 n m o d x = n n\mod x =n nmodx=n,但 y m o d n < n y\mod n<n ymodn<n,与假设矛盾,因此结论一正确。
结论二: n ≤ y n\leq y n≤y。
证明:使用反证法证明,存在某个 n > y n>y n>y 使得 n m o d x = y m o d n n\mod x=y\mod n nmodx=ymodn 成立。则 n m o d x < x n\mod x<x nmodx<x,但 y m o d n = y ≥ x y\mod n=y\geq x ymodn=y≥x,与假设矛盾, 因此结论二正确。
所以 x ≤ n ≤ y x \leq n \leq y x≤n≤y,下面考虑如何求出 n n n 确切的值。
假设有一个 X X X 轴,一开始在 0 0 0 点,从 0 0 0 点跳到 y y y 点,每次跳跃的距离为 x x x。有可能在最后一次跳跃后跳过点 y y y,设最后一次跳跃前的位置为 p = y − y m o d x p=y-y\mod x p=y−ymodx,从位置 p p p 跳跃到 y y y 点需要两个步骤。因为 y − p y-p y−p 是偶数,所以我们需要跳跃 y − p 2 \frac{y-p}{2} 2y−p 个单位,此时跳到了位置 t = p + y − p 2 t=p+\frac{y-p}{2} t=p+2y−p,可以发现 t t t 就是我们要求的 n n n,因为 t m o d x = y − p 2 t\mod x=\frac{y-p}{2} tmodx=2y−p 且 y m o d t = ( y − p ) − y − p 2 = y − p 2 y\mod t=(y-p)-\frac{y-p}{2}=\frac{y-p}{2} ymodt=(y−p)−2y−p=2y−p,即 n = t = y − y m o d x 2 n=t=y-\frac{y\mod x}{2} n=t=y−2ymodx。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int x, y;
cin >> x >> y;
if (x <= y) {
cout << y - y % x / 2 << endl;
} else {
cout << x + y << endl;
}
}
return 0;
}
E.Extreme Extension
题目描述
给一个长为 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n\leq 10^5) n(1≤n≤105) 的序列 a 1 , a 2 , ⋯ , a n ( 1 ≤ a i ≤ 1 0 5 ) a_1,a_2,\cdots,a_n(1\leq a_i\leq 10^5) a1,a2,⋯,an(1≤ai≤105),每次操作可以选择序列中的一个数字 a i a_i ai,将其拆分为两个数字之和并代替 a i a_i ai 加入到序列中。定义一个序列的 极值 为使得数组单调不下降所需的最少操作次数,求序列 a a a 的所有子序列的 极值 之和,答案对 998244353 998244353 998244353 取模。
分析
对于 a i > a i + 1 a_i>a_{i+1} ai>ai+1 的情况,需要将 a i a_i ai 拆成 k k k 个数字: 1 ≤ b 1 ≤ b 2 ≤ ⋯ ≤ b k ≤ a i + 1 1\leq b_1\leq b_2\leq \cdots\leq b_k\leq a_{i+1} 1≤b1≤b2≤⋯≤bk≤ai+1 且 b 1 + b 2 + ⋯ + b k = a i b_1+b_2+\cdots+b_k=a_i b1+b2+⋯+bk=ai。由于 b k ≤ a i + 1 b_k\leq a_{i+1} bk≤ai+1,所以 k ≥ ⌈ a i a i + 1 ⌉ k\geq \lceil\frac{a_i}{a_{i+1}}\rceil k≥⌈ai+1ai⌉ 。显然 b 1 b_1 b1 越大操作的次数就越少(比如 [ 4 , 4 , 4 , 5 ] [4,4,4,5] [4,4,4,5] 比 [ 3 , 3 , 3 , 3 , 5 ] [3,3,3,3,5] [3,3,3,3,5] 要好),所以令 k = ⌈ a i a i + 1 ⌉ k=\lceil\frac{a_i}{a_{i+1}}\rceil k=⌈ai+1ai⌉。注意到 b 1 ≤ ⌊ a i k ⌋ b_1\leq \lfloor\frac{a_i}{k}\rfloor b1≤⌊kai⌋,令 b 1 = ⌊ a i k ⌋ = ⌊ a i ⌈ a i a i + 1 ⌉ ⌋ b_1=\lfloor\frac{a_i}{k}\rfloor=\Bigg\lfloor\frac{a_i}{\big\lceil\frac{a_i}{a_{i+1}}\big\rceil}\Bigg\rfloor b1=⌊kai⌋=⌊⌈ai+1ai⌉ai⌋。经过 k − 1 k-1 k−1 次操作即可将 a i a_i ai 拆分成 [ b 1 , b 2 , ⋯ , b k ] [b_1,b_2,\cdots,b_k] [b1,b2,⋯,bk]。
按照如下步骤求出 极值:
- 倒序遍历 i = n − 1 i=n-1 i=n−1 ~ 1 1 1。
- 答案加 ⌈ a i a i + 1 ⌉ − 1 \lceil\frac{a_i}{a_{i+1}}\rceil-1 ⌈ai+1ai⌉−1。
- 令 a i = ⌊ a i ⌈ a i a i + 1 ⌉ ⌋ a_i=\Bigg\lfloor\frac{a_i}{\big\lceil\frac{a_i}{a_{i+1}}\big\rceil}\Bigg\rfloor ai=⌊⌈ai+1ai⌉ai⌋
时间复杂度为 O ( n ) O(n) O(n),求出所有子序列的 极值 之和的时间复杂度为 O ( n 2 ) O(n^2) O(n2),考虑如何用更优地时间复杂度解决此题。
令 d p ( i , x ) dp(i,x) dp(i,x) 为第 i i i 个数被拆分为最小元素为 x x x 的若干个数,而且以 x x x 为开头的非递减数列的数量。
我们只考虑 x x x 有多少种可能的值(实际只有 O ( 1 0 5 ) O(\sqrt{10^5}) O(105),后面会提到)。对于 x = 1 x=1 x=1 ~ 1 0 5 10^5 105,需要进行如下的状态转移: d p ( i , ⌊ a i ⌈ a i x ⌉ ⌋ ) + = d p ( i + 1 , x ) dp\Bigg(i,\Bigg\lfloor\frac{a_i}{\big\lceil\frac{a_i}{x}\big\rceil}\Bigg\rfloor\Bigg)+=dp(i+1,x) dp(i,⌊⌈xai⌉ai⌋)+=dp(i+1,x)。
实际上 x x x 最多只有 2 1 0 5 2\sqrt{10^5} 2105 种不同的值( ⌊ m 1 ⌋ , ⌊ m 2 ⌋ , ⋯ , ⌊ m m ⌋ \lfloor\frac{m}{1}\rfloor,\lfloor\frac{m}{2}\rfloor,\cdots,\lfloor\frac{m}{m}\rfloor ⌊1m⌋,⌊2m⌋,⋯,⌊mm⌋,整除分块的思想)。因此我们可以在 O ( n 1 0 5 ) O(n\sqrt{10^5}) O(n105) 的时间复杂度内解决此题,而且转移方程之和 i , i + 1 i,i+1 i,i+1 有关,可以使用滚动数组优化掉一维。
最后根据 d p dp dp 数组计算答案,对于 d p ( i + 1 , x ) dp(i+1,x) dp(i+1,x),它对答案的贡献为 i × d p ( i + 1 , x ) × ( ⌈ a i a i + ! ⌉ − 1 ) i\times dp(i+1,x)\times(\lceil\frac{a_i}{a_{i+!}}\rceil-1) i×dp(i+1,x)×(⌈ai+!ai⌉−1),这是因为有 i × d p ( i + 1 , x ) i\times dp(i+1,x) i×dp(i+1,x) 个序列,每个序列都对 a i a_i ai 进行这样的拆分。
时间复杂度 O ( n 1 0 5 ) O(n\sqrt{10^5}) O(n105),空间复杂度 O ( n ) O(n) O(n)。
代码
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
const int mod = 998244353;
int a[300010];
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
gp_hash_table<int, int> dp;
long long ans = 0;
for (int i = n; i >= 1; i--) {
gp_hash_table<int, int> &&dp2 = {};
dp2[a[i]] = 1;
for (auto it:dp) {
int t = (a[i] + it.first - 1) / it.first;
dp2[a[i] / t] += it.second;
ans = ans + ((long long) i * it.second) * (t - 1);
}
ans = ans % mod;
dp = move(dp2);
}
cout << ans << endl;
}
return 0;
}