Codeforces Round 900 (Div. 3) 补题报告

Codeforces Round 900 (Div. 3)

AC 情况

ABCDEFG
AcceptedAcceptedAccepted-(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(n3) 的严格递增正整数数组,要求对于任何一个 i ( 1 ≤ i ≤ n − 2 ) i(1\le i\le n-2) i(1in2) 3 ⋅ a i + 2 3 \cdot a_{i+2} 3ai+2 不能被 a i + a i + 1 a_i+a_{i+1} ai+ai+1 整除。

题解:

考虑奇数序列。

  1. 满足严格递增;
  2. 对于任何一个 i ( i ≤ i ≤ n − 2 ) i(i\le i\le n-2) i(iin2) 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} 3ai+2 仍为奇数。奇数不能被偶数整除,则满足 3 ⋅ a i + 2 3 \cdot a_{i+2} 3ai+2 不能被 a i + a i + 1 a_i+a_{i+1} ai+ai+1 整除。

输出 1 ∼ 2 n − 1 1\sim 2n-1 12n1 的奇数序列即可。

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 1n 之间选出 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=1kix(2(1+k)kx) ,且 ∑ 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=nk+1nix(2(2nk+1)kx),则必然可以满足挑出 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 liri, l i = r i − 1 + 1 l_i=r_{i-1}+1 li=ri1+1), l i l_i li r i r_i ri 表示第 i i i 个区间的左端点和右端点。

然后给定一个整数 q q q 表示操作的次数。

每次操作输入一个整数 x x x.

  1. 找到第 x x x 个元素所在的区间 i ( l i ≤ x ≤ r i ) i(l_i\le x\le r_i) i(lixri);
  2. 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+lix),b=max(x,ri+lix);
  3. 将字符串 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(lr).

然后进行 q q q 次查询。

每次查询由两个数字 k k k l l l 组成,找出最大的索引 r ( l ≤ r ≤ n ) r ( l \le r \le n ) r(lrn),使得 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} aiai&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;
}

赛后总结

熟练运用差分等算法。

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值