目录
AC 情况
A | B | C | D | E | F | G |
---|---|---|---|---|---|---|
Accepted | Accepted | Accepted | -(Accepted) | Accepted | - | - |
赛中概况
第一题、第二题、第三题想了想就写出来了,第四题想半天没有思路,第五题考虑到以前做过的题目做出来了。
解题报告
A. How Much Does Daytona Cost?
情况:
Accepted
题意:
给定一个大小为 n n n 的数组 a a a 和一个整数 k k k,判断 a a a 中是否存在一个非空子段,其中 k k k 是该子段中出现次数最多的整数。
题解:
若 k k k 存在于数组 a a a 中,则必然有一个包含且仅包含整数 k k k 的子段,该子段中 k k k 出现次数最多(因为没有其他整数)。所以判断 k k k 是否在 a a a 中出现即可。
补充概念:
-
子段:元素连续(数组)
-
子序列:元素不一定连续(数组或字符串)
-
子数组:等同于子段
-
子串:元素连续(字符串)
map<int, int> p;
cin >> n >> k;
while (n--) {
cin >> a;
p[a]++;
}
if (p[k]) cout << "YES\n";
else cout << "NO\n";
B. Aleksa and Stack
情况:
Accepted
题意:
构造一个长为 n ( n ≥ 3 ) n(n\ge 3) n(n≥3) 的严格递增正整数数组,要求对于任何一个 i ( 1 ≤ i ≤ n − 2 ) i(1\le i\le n-2) i(1≤i≤n−2), 3 ⋅ a i + 2 3 \cdot a_{i+2} 3⋅ai+2 不能被 a i + a i + 1 a_i+a_{i+1} ai+ai+1 整除。
题解:
考虑奇数序列。
- 满足严格递增;
- 对于任何一个 i ( i ≤ i ≤ n − 2 ) i(i\le i\le n-2) i(i≤i≤n−2): a i a_i ai 和 a i + 1 a_{i+1} ai+1 为奇数,则 a i + a i + 1 a_i+a_{i+1} ai+ai+1 为偶数; a i + 2 a_{i+2} ai+2 为奇数,则 3 ⋅ a i + 2 3 \cdot a_{i+2} 3⋅ai+2 仍为奇数。奇数不能被偶数整除,则满足 3 ⋅ a i + 2 3 \cdot a_{i+2} 3⋅ai+2 不能被 a i + a i + 1 a_i+a_{i+1} ai+ai+1 整除。
输出 1 ∼ 2 n − 1 1\sim 2n-1 1∼2n−1 的奇数序列即可。
for (int i = 1; i <= n; i++) cout << i * 2 - 1 << " ";
C. Vasilije in Cacak
情况:
Accepted
题意:
给出三个整数 n n n、 k k k、 x x x,判断是否能在 1 ∼ n 1\sim n 1∼n 之间选出 k k k 个不同的整数,使其和为 x x x.
题解:
进过验证可知:
若选取前 k k k 个数或者后 k k k 个数,如果 ∑ i = 1 k i ≤ x ( ( 1 + k ) k 2 ≤ x ) \sum_{i=1}^ki\le x(\frac{(1+k)k}{2}\le x) ∑i=1ki≤x(2(1+k)k≤x) ,且 ∑ i = n − k + 1 n i ≤ x ( ( 2 n − k + 1 ) k 2 ≤ x ) \sum_{i=n-k+1}^ni\le x(\frac{(2n-k+1)k}{2}\le x) ∑i=n−k+1ni≤x(2(2n−k+1)k≤x),则必然可以满足挑出 k k k 个数,和等于 x x x.
long long c(long long a, long long b) {
return (a + b) * (b - a + 1) / 2;
}
if (x >= c(1, k) && x <= c(n - k + 1, n)) cout << "YES\n";
else cout << "NO\n";
D. Reverse Madness
情况:
-(Accepted)
题意:
给定一个长度为 n n n 的全为小写英文字母的字符串 s s s 和一个 整数 k k k.
给定两个有 k k k 个元素的数组 l l l 和 r r r,表示将字符串 s s s 不遗漏、不重叠地完全分成 k k k 个区间 ( l 1 = 1 l_1=1 l1=1, r k = n r_k=n rk=n, l i ≤ r i l_i\le r_i li≤ri, l i = r i − 1 + 1 l_i=r_{i-1}+1 li=ri−1+1), l i l_i li 和 r i r_i ri 表示第 i i i 个区间的左端点和右端点。
然后给定一个整数 q q q 表示操作的次数。
每次操作输入一个整数 x x x.
- 找到第 x x x 个元素所在的区间 i ( l i ≤ x ≤ r i ) i(l_i\le x\le r_i) i(li≤x≤ri);
- 令 a = m i n ( x , r i + l i − x ) , b = m a x ( x , r i + l i − x ) a=min(x,r_i+l_i-x),b=max(x,r_i+l_i-x) a=min(x,ri+li−x),b=max(x,ri+li−x);
- 将字符串 s s s 的子串 [ a , b ] [a,b] [a,b] 翻转。
所有操作结束之后输出字符串 s s s.
题解:
问题1:如何快速查找元素 x x x 所在的区间。
利用 map 在输入右端点时,把 [ l i , r i ] [l_i,r_i] [li,ri] 的所有元素都打上标记。
这里的时间复杂度是 O ( n + k ) O(n+k) O(n+k),遍历元素的次数很微小,实质上是一种线性的处理方法。
for (int i = 1; i <= k; i++) cin >> l[i];
for (int i = 1; i <= k; i++) {
cin >> r[i];
for (int j = l[i]; j <= r[i]; j++) mp[j] = i;
}
问题2:如何操作时进行翻转。
核心问题是在线翻转时导致的时间复杂度过高。
考虑摒弃在线翻转的思路,使用离线翻转的策略:差分。利用差分数组 c c c 记录需要反转的元素,保存完所有操作之后进行一次翻转即可。
while (q--) {
cin >> x;
int a = min(x, l[mp[x]] + r[mp[x]] - x);
int b = max(x, l[mp[x]] + r[mp[x]] - x);
c[a]++;
c[b]--;
}
然后对差分数组 c c c 进行前缀和,获得标记数组。
for (int i = 1; i <= n; i++) c[i] += c[i - 1];
问题3:如何进行离线翻转。
再次遍历每一个区间内的元素,对每个元素可能进行反转的子串的前半段(避免重复翻转)进行检索,如果标记是奇数则需要翻转。
这里的时间复杂度仍然是 O ( n + k ) O(n+k) O(n+k),线性。
for (int i = 1; i <= k; i++) {
for (int j = l[i]; j <= (l[i] + r[i]) / 2; j++) {
if (c[j] % 2 == 1) {
swap(s[j - 1], s[l[i] + r[i] - j - 1]);
}
}
}
E. Iva & Pav
情况:
Accepted
题意:
给定一个长为 n n n 的数组。
定义 f ( l , r ) = a l & a l + 1 & … & a r ( l ≤ r ) f(l, r) = a_l\&a_{l+1} \& \dots \& a_r(l\le r) f(l,r)=al&al+1&…&ar(l≤r).
然后进行 q q q 次查询。
每次查询由两个数字
k
k
k 和
l
l
l 组成,找出最大的索引
r
(
l
≤
r
≤
n
)
r ( l \le r \le n )
r(l≤r≤n),使得
f
(
l
,
r
)
≥
k
f(l, r) \ge k
f(l,r)≥k,如果没有,打印-1
。
题解:
问题1:如何快速获得 f ( l , r ) f(l,r) f(l,r) 的值。
根据题意可以发现, a & b = a & a & b a\& b=a\& a\& b a&b=a&a&b,因此属于可重复贡献问题,可以使用ST表,通过数组 f f f 来存储 f ( l , r ) f(l,r) f(l,r).
void solve() {
for (int i = 1; i <= n; i++) f[i][0] = a[i];
int logn = log2(n);
for (int j = 1; j <= logn; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[i][j] = f[i][j - 1] & f[i + (1 << (j - 1))][j - 1];
}
}
}
问题2:如何进行查询操作。
因为 a i ≥ a i & a i + 1 a_i\ge a_i\& a_{i+1} ai≥ai&ai+1,因此可以得知,所有以 a i a_i ai 为起点的 f ( i , r ) f(i,r) f(i,r) 构成的序列,一定是一个有序的不上升序列,可以考虑采用二分的方法查找 r r r.
特殊判断:因为是不上升序列,所以
f
(
i
,
i
)
f(i,i)
f(i,i) (也就是起点
a
i
a_i
ai)一定是最大的,如果这个值小于
x
x
x,那么必然不存在满足要求的答案,打印-1
即可。
int find(int l, int r, int tmp) {
int L = l;
if (tmp > f[L][0]) return -1;
while (l < r) {
int mid = (l + r + 1) >> 1;
int K = log2(mid - L + 1);
int ans = f[L][K] & f[mid - (1 << K) + 1][K];
if (ans >= tmp) l = mid;
else r = mid - 1;
}
return l;
}
赛后总结
熟练运用差分等算法。