題解 CF1279F New Year and Handle Change
阿新 • • 發佈:2022-03-27
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;
}