1. 程式人生 > 其它 >題解 CF1279F New Year and Handle Change

題解 CF1279F New Year and Handle Change

wqs 二分模板題。

我們可以將大小寫分開做,分別求小寫時的 min 和大寫時的 min。

首先可以發現我們肯定是操作越多次越好,所以我們可以假設正好操作 $k$ 次。設 $f(x)$ 為操作 $x$ 次時的最優答案,我們可以發現 $(x, f(x))$ 構成的函式影象是個下凸殼:

證明:首先 $f(x)$ 肯定是單減的,然後因為你每次操作肯定選最優的操作所以 $f(x)$ 減小的趨勢肯定是越來越小的。

然後就是 wqs 二分,我們二分與 $(k, f(k))$ 相切的直線的斜率 $mid$,然後問題就轉化為求出這條直線的截距,相當於每次操作每次操作多一個 $-mid$ 的價值,直接 dp 即可,$dp_i$ 表示 $1...i$ 的最優答案。轉移是簡單的,需注意轉移時還需要記錄一個當前 $dp_i$ 操作了多少次。

我們求出了截距直接根據斜率即可推出 $f(k)$。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
using ll = long long;
const int N = 1e6 + 5;
int n, m, len, a[N], ans = 1e9;
char s[N];
pair <int, int> dp[N];
int check(int mid) {
    for (int i = 1; i <= n; i++) {
        pair 
<int, int> tmp = dp[i - 1]; tmp.fi += a[i], dp[i] = tmp; tmp = dp[max(i - len, 0)]; tmp.fi -= mid, tmp.se++; dp[i] = min(dp[i], tmp); } return dp[n].se; } void solve() { int l = -n, r = 0, p; while (l <= r) { int mid = l + r >> 1;
if (check(mid) <= m) l = mid + 1, p = mid; else r = mid - 1; } check(p); ans = min(ans, dp[n].fi + p * m); } int main() { scanf("%d%d%d%s", &n, &m, &len, s + 1); for (int i = 1; i <= n; i++) a[i] = s[i] >= 'a' && s[i] <= 'z'; solve(); for (int i = 1; i <= n; i++) a[i] ^= 1; solve(); printf("%d\n", ans); return 0; }