Codeforces Round 1011 (Div. 2)


A. Serval and String Theory

题意:给你一个字符串\(s\),你每次可以交换其中两个位置的字符,最多操作\(k\)次,求可不可以使得\(s\)的字典序比\(res(s)\)小。其中\(rev(s)\)\(s\)翻转后的的字符串。

首先如果\(s\)的所有位置都是一样的,无解。
否则如果\(s[n]\)都是所有字符里最小的,那么把\(s[n]\)换成最大的字符,否则\(s[1]\)是最小的,无需操作,都不是最小的,把\(s[1]\)换成最小的就行。
于是我们最多需要一次操作。注意如果\(s < rev(s)\)那么我们也不需要操作。

点击查看代码
void solve() {
    int n, k;
    std::cin >> n >> k;
    std::string s;
    std::cin >> s;
    std::string t = s;
    std::reverse(t.begin(), t.end());
    std::set<char> set;
    for (auto & c : s) {
    	set.insert(c);
    }

    if (set.size() == 1 || (t <= s && k == 0)) {
    	std::cout << "NO\n";
    } else {
    	std::cout << "YES\n";
    }
}

B. Serval and Final MEX

题意:给你一个数组,每次选一个区间,把这个区间的都删掉,然后插入它们的\(mex\)。要让最后只剩下一个\(0\)。求操作方案。

分类讨论。
首先找到最左边和最右边的\(0\),记为\(l, r\)
如果没有\(0\),那么直接一次操作\([1, n]\)就行。
否则如果\(l=r\),如果\(l=1\)则需要先操作\([l, n - 1]\),然后剩下就没有\(0\)了。否则如果\(l=n\),先操作\([2, n]\),剩下就没有\(0\)了。
如果\(l!=r\)也是类似上面的讨论,特判有没有\(0\)在边界的情况。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    int l = 0, r = n - 1;
    while (l < r && a[l] != 0) {
    	++ l;
    }

    while (l < r && a[r] != 0) {
    	-- r;
    }

    if (a[l] != 0) {
    	std::cout << 1 << "\n";
    	std::cout << 1 << " " << n << "\n";
    } else if (l == r) {
    	std::cout << 2 << "\n";
    	if (l == 0) {
    		std::cout << 1 << " " << n - 1 << "\n";
    		std::cout << 1 << " " << 2 << "\n";
    	} else if (l == n - 1) {
    		std::cout << 2 << " " << n << "\n";
    		std::cout << 1 << " " << 2 << "\n";
    	} else {
    		std::cout << 2 << " " << n << "\n";
    		std::cout << 1 << " " << 2 << "\n";
    	}
    } else {
    	if (l == 0 && r == n - 1) {
	    	std::cout << 3 << "\n";
	    	std::cout << 3 << " " << n << "\n";
	    	std::cout << 1 << " " << 2 << "\n";
	    	std::cout << 1 << " " << 2 << "\n";
	    } else if (l == 0) {
	    	std::cout << 2 << "\n";
	    	std::cout << 1 << " " << n - 1 << "\n";
	    	std::cout << 1 << " " << 2 << "\n";
	    } else if (r == n - 1) {
	    	std::cout << 2 << "\n";
	    	std::cout << 2 << " " << n << "\n";
	    	std::cout << 1 << " " << 2 << "\n";
	    } else {
	    	std::cout << 3 << "\n";
	    	std::cout << r + 1 << " " << n << "\n";
	    	std::cout << 1 << " " << r << "\n";
	    	std::cout << 1 << " " << 2 << "\n";
	    }
    }
}

C. Serval and The Formula

题意:给你\(x, y\),找一个\(k\),使得\((x + k) + (y + k) = (x + k) \oplus (y + k)\)

首先\(a + b \geq a \oplus b\),大于的情况是\(a+b\)产生了进位。等于的情况则是没有进位,如果没有进位,那么\(a, b\)每一位都不同时为\(1\)

那么显然\(x=y\)是无解的,那么如果\(x\ne y\)我们可以求一个\(s\)使得\(s\)是第一个大于等于\(\max(x, y)\)\(2\)的幂的数。那么\(k\)可以等于\(s - \max(x, y)\)。假设\(x > y\),那么\(x + k\)后变成了\(s\)\(y + k<s\),而\(s\)除了最高位其它位都是\(0\),所以符合条件。

点击查看代码
void solve() {
    i64 x, y;
    std::cin >> x >> y;

    if (x == y) {
    	std::cout << -1 << "\n";
    	return;
    }
    i64 n = std::max(x, y);

    i64 s = 1;
    while (s < n) {
        s <<= 1;
    }

    i64 k = s - n;
    std::cout << k << "\n";
}

D. Serval and Kaitenzushi Buffet

题意:有\(n\)个物品,每个物品有一个价值,你按顺序操作,每次选择拿走一个物品,或者消化之前拿过的某个物品,或者上面都不做。每个物品需要\(k\)次才能消化,最后你要恰好消耗拿过的所有物品,求最大价值。

如果我们选择了\(m\)个物品,那么我们总共需要\(m \times (k + 1)\)次操作。但并不是操作数满足就合法,因为我们需要每个后缀都能满足,也就是说,只拿最后一个物品可以满足条件,只拿最后两个物品可以满足条件... 那么我们可以从后往前枚举,最后一个我们只能在\([1, n - k]\)里拿,最后两个我们只可以在\([1, n - 2k - 1]\)里拿... 那么我们把数组分成\(\lfloor \frac{n}{k+1} \rfloor\)段,从后往前找每一段的最右边可以拿到的位置\(i\),那么这一段可以在\([i - k, i]\)拿一个数。我们还需要考虑用当前区间的某个数换之前的数,因为我们用更前面的数换后面的数肯定也合法。那么算法如下:
我们用线段树维护区间最大值。如何从后往前枚举,用一个\(multiset\)存已经拿过的数,每次操作一个新区间,如果这个区间的最大值比之前拿到过的数最小值大,我们就不断更换,每次更换后把最大值的位置修改成\(0\)
总共有\(\lfloor \frac{n}{k+1} \rfloor\)个区间,每个区间最多操作\(k + 1\)次,那么时间复杂度就是线段树的复杂度,是\(nlogn\)

点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)

template <class Info>
struct Node {
	int l, r;
	Info info;
};

template <class Info>
struct SegmentTree {
	std::vector<Node<Info> > tr;
	SegmentTree(int _n) {
		init(_n);
	}

	SegmentTree(std::vector<Info> & a) {
		init(a);
	}

	void init(int _n) {
		tr.assign(_n << 2, {});
		build(0, _n - 1);
	}

	void init(std::vector<Info> & a) {
		int _n = (int)a.size();
		tr.assign(_n << 2, {});
		build(0, _n - 1, a);
	}

	void pushup(int u) {
		tr[u].info = tr[ls].info + tr[rs].info;
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, ls); build(mid + 1, r, rs);
	}

	void build(int l, int r, std::vector<Info> & a, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			tr[u].info = a[l];
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, a, ls); build(mid + 1, r, a, rs);
		pushup(u);
	}

	void modify(int p, Info add, int u = 1) {
		if (tr[u].l == tr[u].r) {
			tr[u].info = add;
			return;
		}

		int mid = umid;
		if (p <= mid) {
			modify(p, add, ls);
		} else {
			modify(p, add, rs);
		}

		pushup(u);
	}

	Info query(int l, int r, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].info;
		}

		int mid = umid;
		if (r <= mid) {
			return query(l, r, ls);
		}  else if (l > mid) {
			return query(l, r, rs);
		}

		return query(l, r, ls) + query(l, r, rs);
	}
};

struct Info {	
	i64 max, p;
};

Info operator + (const Info & a, const Info & b) {
	Info res{};
	if (a.max >= b.max) {
		res = a;
	} else {
		res = b;
	}
	return res;
}

void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector<i64> a(n);
    std::vector<Info> info(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	info[i] = {a[i], i};
    }

    SegmentTree<Info> tr(info);

    i64 ans = 0, sum = 0;
    std::multiset<i64> s;
   	for (int i = n - 1 - k; i >= 0; i -= k + 1) {
   		ans = std::max(ans, sum + tr.query(0, i).max);
   		int l = std::max(0, i - k);
   		s.insert(0);
		while (tr.query(l, i).max > *s.begin()) {
			sum -= *s.begin();
			s.erase(s.begin());
			auto v = tr.query(l, i);
			sum += v.max;
			tr.modify(v.p, Info{0, v.p});
			s.insert(v.max);
		}
		ans = std::max(ans, sum);
   	}

   	std::cout << ans << "\n";
}

E. Serval and Modulo

题意:给你两个数组\(a, b\),求是否存在一个\(k, k \in [1, 10^9]\),使得\(b\)重排后与\(b_i = a_i \% k\)

赛后补题
这题其实挺简单的。如果放\(c\)应该能过很多人。
只要发现对应的\(b_i\)\(a_i\)少的值是\(k\)的倍数这题基本上就做完了。
那么根据上面的结论,\(sum_a - sum_b\)应该也是\(k\)的倍数,那么我们暴力枚举\(sum_a - sum_b\)的因子判断就行了。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n), b(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    }

    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());

    i64 suma = std::accumulate(a.begin(), a.end(), 0ll);
    i64 sumb = std::accumulate(b.begin(), b.end(), 0ll);

    if (suma - sumb < 0) {
    	std::cout << -1 << "\n";
    	return;
    } else if (suma == sumb) {
    	if (a == b) {
    		int ans = 1e9;
    		std::cout << ans << "\n";
    	} else {
    		std::cout << -1 << "\n";
    	}
    	return;
    } 

    auto check = [&](i64 k) -> bool {
    	if (k > 1e9) {
    		return false;
    	}

    	auto t = a;
    	for (auto & x : t) {
    		x %= k;
    	}

    	std::sort(t.begin(), t.end());
    	return t == b;
    };

    i64 d = suma - sumb;
    for (i64 i = 1; i * i <= d; ++ i) {
    	if (d % i == 0) {
    		if (check(i)) {
    			std::cout << i << "\n";
    			return;
    		}

    		if (check(d / i)) {
    			std::cout << d / i << "\n";
    			return;
    		}
    	}
    }

    std::cout << -1 << "\n";
}
posted @ 2025-03-23 01:05  maburb  阅读(343)  评论(0)    收藏  举报