CF2149E. Hidden Knowledge of the Ancients
思路
滑动窗口 + 双指针。
先不考虑长度的限制,求"恰好有 \(k\) 个不同的数"的区间。可以维护两个窗口,一个是以当前的位置为右端点,且第一个最多有 \(k\) 个不同元素 的区间;一个是以当前位置为右端点,且第一个 最多有 \(k - 1\) 个不同元素 的区间。那么以当前位置为右端点,且恰好有 \(k\) 个不同元素的子区间的数量就为 最多 \(k - 1\) 个 - 最多 \(k\) 个。其实就是它们的左端点相减的结果。再加上长度限制,要满足
\[l \leq (右端点 - 左端点 + 1) \leq r \Rightarrow 右 - r + 1 \leq 左 \leq 右 - l + 1
\]
所以对于当前的右端点,合法的左端点的数量就为 \(\min(最多 k - 1 个的左端点, c - l + 1) - \max(最多 k 个的右端点, c - r + 1)\) 。
由于 \(a\) 的范围很大,所以要先离散化。
代码
void solve(void) {int n, k, l, r;std::cin >> n >> k >> l >> r;std::vector<int> a(n);for(int i = 0; i < n; i++) std::cin >> a[i];auto tmp = a;std::sort(tmp.begin(), tmp.end());tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());for(auto &i : a) {i = std::lower_bound(tmp.begin(), tmp.end(), i) - tmp.begin();}int len = tmp.size();i64 ans = 0;std::vector<int> K(len), K1(len);int disk = 0, disk1 = 0;int lk = 0, lk1 = 0;for(int i = 0; i < n; i++) {K[a[i]]++;if(K[a[i]] == 1) {disk++;}while(disk > k) {int x = a[lk++];K[x]--;if(K[x] == 0) {disk--;}}K1[a[i]]++;if(K1[a[i]] == 1) {disk1++;}while(disk1 > k - 1) {int x = a[lk1++];K1[x]--;if(K1[x] == 0) {disk1--;}}int L = std::max(lk, i - r + 1);int R = std::min(lk1 - 1, i - l + 1);if(R >= L) ans += R - L + 1;}std::cout << ans << '\n';
}
CF2149F. Nezuko in the Clearing
思路
二分。
首先注意到,无论如何都是要走 \(d\) 个回合的,但是在这期间可能会休息若干次。因此可以表示为将 \(d\) 个回合分割为了 \(n\) 段,每段连续走 \(k_i\) 个回合,然后停下来休息一次,最后的答案就应该是:
\[d + (n - 1)
\]
在这期间,血量的总消耗是:
\[\sum_{i = 1}^{n}\frac{k_i(k_i + 1)}{2} - (n - 1)
\]
要保证消耗 \(\leq h - 1\) ,这样才能保证每一次触发的时候都有 \(\geq 1\) 的血量。
显然我们需要枚举分段的数量,如何 check 呢?
观察函数 \(f(k) = \frac{k(k + 1)}{2}\) ,我们想要让 \(\sum_{i = 1}^{n} f(k_i)\) 最小,注意到应该让每一段的长度都尽可能的接近,也就是说我们会分成若干段 \(\lfloor \frac{d}{n} \rfloor\) ,还有一段 \(d \mod n\) ,然后就可以判断了。
但是如何证明这个结论呢?我不会
代码
void solve(void) {i64 h, d;std::cin >> h >> d;if(h == 1) {std::cout << d * 2 << '\n';return;}i64 l = 1, r = d, len = -1;auto check = [&](i64 x) -> bool {i64 q = d / x;i64 r = d % x;i64 sum = r * (q + 1) * (q + 2) / 2;i64 sum1 = (x - r) * q * (q + 1) / 2;i64 total = sum + sum1 - (x - 1);return total <= h - 1;};while(l <= r) {i64 mid = (l + r) / 2;if(check(mid)) {r = mid - 1;len = mid;} else l = mid + 1;}//std::cout << len << '\n';std::cout << (len - 1) + d << '\n';
}