Codeforces Round #830 (Div. 2) A~D2 题解

A. Bestie

题意:

给一个长度为 n n n 的整数序列 S S S,并定义一种操作:
选择一个下标 i ( 1 ≤ i ≤ n ) i(1\leq i\leq n) i(1in),使 S [ i ] = g c d ( S [ i ] , i ) S[i]=gcd(S[i],i) S[i]=gcd(S[i],i),代价为 n − i + 1 n-i+1 ni+1
问花费最小的代价,使得序列所有数的 g c d gcd gcd 值为 1 1 1

思路:

首先有一个比较有用的性质,即相邻两个数互质,也就是说他们的 g c d gcd gcd 值一定为 1 1 1,那么意味着操作最多被执行两次。
这样题目就很清晰了,枚举花费为 0 , 1 , 2 , 3 ( 1 + 2 ) 0,1,2,3(1+2) 0,1,2,3(1+2) 的情况即可。

时间复杂度: O ( n ) O(n) On

int n;
int s[N], d[N];
 
signed main() {
	IOS
	cf{
		sf(n);
		for (int i = 1; i <= n; i++)
			sf(s[i]);
 
		int e = s[1];
		for (int i = 2; i <= n; i++)
			e = __gcd(e, s[i]);
		if (e == 1) {
			pfn(0);
			continue;
		}
		
		int a = __gcd(s[n], n);
		for (int i = 1; i < n; i++)
			a = __gcd(a, s[i]);
		if (a == 1) {
			pfn(1);
			continue;
		}
 
		if (n > 1) {
			a = __gcd(s[n - 1], n - 1);
			for (int i = 1; i < n - 1; i++)
				a = __gcd(a, s[i]);
			a = __gcd(a, s[n]);
			if (a == 1) {
				pfn(2);
				continue;
			}
 
			a = __gcd(s[n - 1], n - 1);
			a = __gcd(a, __gcd(s[n], n));
			for (int i = 1; i < n - 1; i++)
				a = __gcd(a, s[i]);
			if (a == 1) {
				pfn(3);
				continue;
			}
		}
	}
	return 0;
}

B. Ugu

题意:

给一个长度为 n n n 01 01 01 字符串 S S S,并定义一种操作:
选择一个下标 i ( 1 ≤ i ≤ n ) i(1\leq i\leq n) i(1in),并使所有 S [ j ] ( i ≤ j & j ≤ n ) S[j](i\leq j \& j\leq n) S[j](ij&jn) 反转,也就是 0 − > 1 、 1 − > 0 0->1、1->0 0>11>0
问最少操作次数使 S S S 变成非严格意义递增字符串。

思路:

首先先排除初始符合条件的情况。
然后观察样例,会发现最小操作次数和 01 / 10 01/10 01/10 的转折点个数有关,这个从结果倒推原因很简单,就不过多赘述了。
要注意当所有转折点均反转的话,最终字符串的所有字符会全部相同,而 0 … … 01 … … 1 0……01……1 0……01……1 的情况同样是合法的,特判下即可。

时间复杂度: O ( n ) O(n) On

int n;
char s[N];
 
signed main() {
	IOS
	cf{
		sf(n);
		sf(s + 1);
 
		bool st;
		if (s[1] == '0')
			st = false;
		else
			st = true;
		int cnt = 0;
		for (int i = 2; i <= n; i++)
			if (s[i] != s[i - 1])
				cnt++;
 
		if (cnt == 0 || (st == false && cnt == 1)) {
			pfn(0);
			continue;
		}
		if (st)
			pfn(cnt);
		else
			pfn(cnt - 1);
	}
	return 0;
}

C1. Sheikh (Easy version)

题意:

给一个长度为 n n n 的非负整数序列 S S S,并定义一种函数:
f ( l , r ) = ( S [ l ] + S [ l + 1 ] + … … + S [ r ] ) − ( S [ l ] ⊕ S [ l + 1 ] ⊕ … … ⊕ S [ r ] ) f(l,r)=(S[l]+S[l+1]+……+S[r])-(S[l]\oplus S[l+1]\oplus……\oplus S[r]) f(l,r)=(S[l]+S[l+1]+……+S[r])(S[l]S[l+1]……S[r])
然后给出一个区间 [ L , R ] [L,R] [L,R] ,问找到一子区间 [ l , r ] ( L ≤ l ≤ r ≤ R ) [l,r](L\leq l\leq r\leq R) [l,r](LlrR) ,使其在 f ( l , r ) f(l,r) f(l,r) 值最大的情况下, r − l + 1 r-l+1 rl+1 的值最小。

思路:

确定一点,由于异或和加法的特点, m a x f ( l , r ) max{f(l,r)} maxf(l,r) 一定等于 f ( L , R ) f(L,R) f(L,R) ,那直接双指针扫一遍即可。

时间复杂度: O ( n ) O(n) On

int n, m;
int s[N], d[N], e[N];
int L, R;
int get(int l, int r) {
	return d[r] - d[l - 1] - (e[r] ^ e[l - 1]);
}

signed main() {
	IOS
	cf{
		sf2(n, m);
		for (int i = 1; i <= n; i++)
			sf(s[i]), d[i] = d[i - 1] + s[i], e[i] = e[i - 1] ^ s[i];
			
		sf2(L, R);
		int ma = get(L, R);
		int a = L, b = R;
		for (int i = L, r = L; i <= R; i++) {
			while (r < i)
				r++;
			while (r <= R && get(i, r) != ma)
				r++;
			if (r > R)
				break;
			if (r - i < b - a) {
				b = r;
				a = i;
			}
		}
		pp(a, b);
	}
	return 0;
}

C2. Sheikh (Hard Version)

题意:

题意与 C 1 C1 C1 相同,只不过询问的区间由 1 1 1 个变为 n n n 个。

思路:

首先,序列中值为 0 0 0 的下标是完全没有用处的。所以我们可以重载下标,将所有值为 0 0 0 的下标省去,用数组模拟或链表均可,注意每个下标应当映射为的左右边界是不同的。

由于数组中的所有元素的值均大于零,那么接下来就是本题最重要的一个性质,即 f ( L , R ) > f ( L + 30 , R ) f(L,R)>f(L+30,R) f(L,R)>f(L+30,R) 或者这样表示 f ( L , R ) > f ( L , R − 30 ) f(L,R)>f(L,R-30) f(L,R)>f(L,R30)(假使下标均合法)。
因为 1 < S [ ] ≤ 1 0 9 1<S[]\leq 10^9 1<S[]109,即转化为二进制最大有 30 30 30 位,那么由于鸽笼原理, 31 31 31 个正数中一定有两个数,它们的二进制上有一位均为 1 1 1 ,也就是说,只要区间长度相差 31 31 31 以上, f ( , ) f(,) f(,) 的值必然会变化。
那么思路就很明确了,枚举所有可能是解的 31 ∗ 31 31*31 3131 对下标即可。

尤其要注意,由于当前使用的下标均为重载之后的新下标,在比较区间长度时必须还原到初始下标进行计算。

时间复杂度: O ( n ∗ 3 1 2 ) O(n*31^2) On312

int n, m;
int s[N], d[N], e[N];
int L, R;
int idl[N], idr[N];
int re[N];
int get(int l, int r) {
	return d[r] - d[l - 1] - (e[r] ^ e[l - 1]);
}

signed main() {
	IOS
	cf{
		sf2(n, m);
		int len = 0;
		for (int i = 1, x; i <= n; i++) {
			sf(x);

			if (x > 0) {
				s[++len] = x;
				d[len] = d[len - 1] + s[len];
				e[len] = e[len - 1] ^ s[len];
				idl[i] = idr[i] = len;
				re[len] = i;
			} else {
				idl[i] = len + 1;
				idr[i] = len;
			}
		}

		while (m--) {
			sf2(L, R);

			int l = idl[L], r = idr[R];
			if (l > r || l == n + 1) {
				pp(L, L);
				continue;
			}

			int ma = get(l, r);
			if (ma == 0) {
				pp(L, L);
				continue;
			}

			int a = l, b = r;
			for (int i = 0 + l; i <= 30 + l && i <= len; i++)
				for (int j = r - 0; j >= r - 30 && j >= 1; j--)
					if (i <= j && get(i, j) == ma && re[j] - re[i] < re[b] - re[a]) {
						b = j;
						a = i;
					}

			pp(re[a], re[b]);
		}
	}
	return 0;
}

D1. Balance (Easy version)

题意:

需要实现一个数据结构,至此以下操作:

  • +   x +\ x + x:添加 x x x 到数据结构中,保证 x x x 没有出现过。
  • ?  k ?\ k  k:查询最小未出现的 k k k 的倍数。

思路:

用离散化数据结构保存一个数是否出现过,这一步可以由 s e t set set m a p map map 来实现;
由调和级数可知,如果暴力查找的话,最多进行 ∑ i = 1 n n i ≤ n ∗ log ⁡ n \sum\limits_{i=1}^{n}{\frac{n}{i}}\leq n*\log{n} i=1ninnlogn 次加法。
因此直接暴力即可。

时间复杂度: O ( n ∗ log ⁡ n ) O(n*\log{n}) Onlogn

char op[2];
int x;
map<int, int> d;
set<int> s;

signed main() {
	IOS
	cf{
		sf2(op, x);

		if (op[0] == '+') {
			s.insert(x);
		} else {
			if (d.count(x) == 0)
				d[n] = x;
			while (s.find(d[x]) != s.end())
				d[x] += x;

			pfn(d[x]);
		}
	}
	return 0;
}

D2. Balance (Hard version)

题意:

D 1 D1 D1 类似,只不过添加了一种操作:

  • −   x -\ x  x:删除 x x x 从数据结构中,保证 x x x 之前出现过。

思路:

这道题是看佬的题解补的,所以解释可能不是太准确。

因为有些曾经存在过的数 X X X 被删除,使得一些出现过的 X X X 的因数 y y y y − m e x y-mex ymex 的值会变化,为了处理这种情况,就需要用到计算普通的 m e x mex mex 值用到的方法,即将未出现的符合条件的值放入 set<int> del 中,那么只要它不空,那么对应的 *del[y].begin() 就是所求的 y − m e x y-mex ymex 的值。

那么怎么去更新 del[y] 中的值呢?我们需要保存,哪些数使 y − m e x y-mex ymex 的值发生改变,这时我们发现这些数就是 X X X ,因此我们只需要在求 y − m e x y-mex ymex 的值的时候,将 y y y 放入那些存在的、影响 y − m e x y-mex ymex 的数,也就是 X X X ,的 vector<int> change 中,那么当 X X X 被删除的时候,那些 d e l [ y ] del[y] del[y] 就需要更新,这时就可以从 change[X] 中去找 y y y

更详细的 s t l stl stl 容器解释在代码注释中。

时间复杂度: O (不好说) O(不好说) O(不好说)

int x;
char op[2];
map<int, int>vis;
//当前是否存在X
map<int, int>last;
//层算过的最大的X-mex
map<int, set<int>>del;
//出现过,但是当前不存在的X的倍数
map<int, vector<int>>change;
//存放X的出现过的因数,这些因数的答案曾因为X而改变

signed main() {
	IOS
	cf{
		sf2(op, x);
		if (op[0] == '+') {
			vis[x] = 1;
			for (int y : change[x]) {
				del[y].erase(x);
			}
		} else if (op[0] == '-') {
			vis[x] = 0;
			for (int y : change[x]) {
				del[y].insert(x);
			}
		} else {
			if (!last.count(x))
				last[x] = x;
			if (del[x].size()) {
				cout << *del[x].begin() << endl;
			} else {
				while (vis[last[x]]) {
					change[last[x]].push_back(x);
					last[x] += x;
				}
				cout << last[x] << endl;
			}
		}
	}
	return 0;
}

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值