VP Educational Codeforces Round 66 (Rated for Div. 2)


A. From Hero to Zero

题意:你要把\(n\)变成零,每次有两种操作:一种是把\(n\)减一,一种是如果\(k\)整除\(n\)则把\(n\)除以\(k\)。求最小操作数。

能除就除,不能除就减到最近的\(k\)的倍数。

点击查看代码
void solve() {
    i64 n, k;
    std::cin >> n >> k;
    i64 ans = 0;
    while (n > 0) {
    	while (n % k == 0) {
    		++ ans;
    		n /= k;
    	}
    	ans += n - n / k * k;
    	n = n / k * k;
    }

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

B. Catch Overflow!

题意:一个编程语言只有三种语句:add, for x, end,要求你输出这些语句的结果。

用栈存每个\(for\)循环自己这一层的\(add\)个数和与外层的乘积。注意溢出。把乘积和\(2^{31}\)次方取最小值。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    i64 ans = 0;
    i64 max = (1ll << 32) - 1;
    std::stack<std::pair<i64, i64>> stk;
    for (int i = 0; i < n; ++ i) {
    	std::string s;
    	std::cin >> s;
    	if (s == "add" && stk.empty()) {
    		ans += 1;
    		if (ans > max) {
	    		std::cout << "OVERFLOW!!!\n";
	    		return;
	    	}
    	} else {
    		if (s == "for") {
    			i64 x;
    			std::cin >> x;
				if (stk.empty()) {
					stk.emplace(0, x);
				} else {
					stk.emplace(0, std::min(max + 1, x * stk.top().second));
				}
    		} else if (s == "add") {
    			stk.top().first += 1;
    		} else {
    			auto [x, y] = stk.top();
    			if (x * y > max || ans + x * y > max) {
    				std::cout << "OVERFLOW!!!\n";
    				return;
    			}

    			ans += x * y;
	    		if (ans > max) {
		    		std::cout << "OVERFLOW!!!\n";
		    		return;
		    	}
    			stk.pop();
    		}
    	}
    }

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

C. Electrification

题意:给你\(n\)个点,求一个点使得这\(n\)个点距离它\(k+1\)远的点的距离最小。

这个点距离\(k+1\)小的这些点在序列里一定是连续的。于是我们可以枚举每个\(k+1\)的子数组。要让距离最小,显然取中位数。

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

    ++ k;
    int d = 2e9, ans = 0;
    for (int i = 0; i + k - 1 < n; ++ i) {
    	int x = a[i] + a[i + k - 1] >> 1;
    	if (d > std::max(x - a[i], a[i + k - 1] - x)) {
    		d = std::max(x - a[i], a[i + k - 1] - x);
    		ans = x;
    	}
    }

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

D. Array Splitting

题意:把数组分成\(k\)段,如果\(a_i\)在第\(j\)段,贡献为\(a_i \times j\)。求最大贡献和。

考虑把\(a_i \times j\)拆为\(j\)\(a_i \times 1\)。那么如果以\(i\)这个位置新开一段,则\([i, n]\)这个后缀的数字都要加一次。于是我们必选\([1, n]\)这个后缀,然后选\(k-1\)个最大的后缀就行。

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

    std::vector<i64> b;
    i64 suf = 0;
    for (int i = n - 1; i > 0; -- i) {
    	suf += a[i];
    	b.push_back(suf);
    }

    i64 ans = suf + a[0];
    std::ranges::sort(b, std::greater<>());
    for (int i = 0; i + 1 < k; ++ i) {
    	ans += b[i];
    }

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

E. Minimal Segment Cover

题意:给你\(n\)个区间,对于每个询问的\([l, r]\),求至少需要从中选几个区间才能覆盖它。

考虑倍增,\(f[i][j]\)表从小于等于\(i\)的位置选\(2^j\)个区间能覆盖到的最远的右端点。
那么就做完了。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    const int N = 5e5;
    std::vector f(N + 1, std::vector<int>(20));
    for (int i = 0; i < n; ++ i) {
    	int l, r;
    	std::cin >> l >> r;
    	f[l][0] = std::max(f[l][0], r);
    }

    for (int i = 1; i <= N; ++ i) {
    	f[i][0] = std::max(f[i][0], f[i - 1][0]);
    }

    for (int j = 1; j < 20; ++ j) {
    	for (int i = 0; i <= N; ++ i) {
    		f[i][j] = f[f[i][j - 1]][j - 1];
    	}
    }

    while (m -- ) {
    	int x, y;
    	std::cin >> x >> y;
    	int ans = 0, p = x;
    	for (int i = 19; i >= 0; -- i) {
    		if (f[p][i] < y) {
    			ans += 1 << i;
    			p = f[p][i];
    		}
    	}

    	if (f[p][0] < y) {
    		std::cout << -1 << "\n";
    	} else {
    		std::cout << ans + 1 << "\n";
    	}
    }
}

F. The Number of Subpermutations

题意:求一个数字的子数组是一个排列的数量。

一个长度为\(n\)的数组是排列,那么需要两个条件:

  1. 最大值为\(n\)
  2. 每个数都只出现一次。

那么对于一个区间\([l, r]\),我们以一个最大值的位置作为\(mid\),把区间分为\([l, mid - 1]\)\([mid + 1, r]\)两部分,然后我们再计算当前区间跨过\(mid\)的区间的贡献。我们用\(last_i\)记录\(a_i\)前一次出现的位置,\(pre_i = \max_{j=1}^{i} last_j\)。那么对于一个区间\([l, r]\),如果\(pre_r < l\),则这个区间的数都只出现一次,因为每个数的前一次出现的位置都小于\(l\)。那么就满足了作为排列的两个条件。
我们选择左区间和右区间的长度小的那个来枚举端点,时间复杂度是\(O(nlogn)\)

点击查看代码
template <class Info> 
struct ST {
	std::vector<std::vector<Info>> st;
	ST(std::vector<Info> a) {
		int n = a.size(), m = std::__lg(n) + 1;
		st.assign(n, std::vector<Info>(m));
		for (int i = 0; i < n; ++ i) {
			st[i][0] = a[i];
		}

		for (int j = 1; j < m; ++ j) {
			for (int i = 0; i + (1 << j - 1) < n; ++ i) {
				st[i][j] = st[i][j - 1] + st[i + (1 << j - 1)][j - 1];
			}
		}
	}

	Info query(int l, int r) {
		int lg = std::__lg(r - l + 1);
		return st[l][lg] + st[r - (1 << lg) + 1][lg];
	}
};

struct Info {
	int max, p;
};

Info operator + (const Info & a, const Info & b) {
	return a.max >= b.max ? a : b;
}

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	-- a[i];
    }

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

    std::vector<int> last(n, -1), pre(n, -1);
    for (int i = 0; i < n; ++ i) {
    	if (i > 0) {
    		pre[i] = std::max(pre[i - 1], last[a[i]]);
    	}
    	last[a[i]] = i;
    }

    ST<Info> st(info);
    auto dfs = [&](auto & self, int l, int r) -> i64 {
    	if (l > r) {
    		return 0;
    	}

    	int mid = st.query(l, r).p;
    	i64 res = self(self, l, mid - 1) + self(self, mid + 1, r);
    	int len = a[mid] + 1;
    	if (mid - l < r - mid) {
    		for (int i = std::max(l, mid - len + 1); i <= mid && i + len - 1 <= r; ++ i) {
    			if (pre[i + len - 1] < i) {
    				++ res;
    			}
    		}
    	} else {
    		for (int i = std::min(r, mid + len - 1); i - len + 1 >= l && i >= mid; -- i) {
    			if (pre[i] < i - len + 1) {
    				++ res;
    			}
    		}
    	}

    	return res;
    };

    std::cout << dfs(dfs, 0, n - 1) << "\n";
}
posted @ 2025-04-23 17:55  maburb  阅读(10)  评论(0)    收藏  举报