1. 程式人生 > 實用技巧 >SM 2020.9.9

SM 2020.9.9

link

T1

T2

\(f(i)\) 表示 \([1,i]\) 被凍住,\((i,n]\) 未被凍住的最少次數。
\(f(i) = \min \begin{cases} \infty & s_i = \texttt{1} \\ f(i-1) + i & s_i = \texttt{0}\\ \min\limits_{j\in [i-2k-1,i)}\{f(j)\} + i-k & s_{i-k} = \texttt{1} \end{cases}\)
單調佇列維護一下就是 \(O(n)\) 了。
注意要做到 \(f(n+k)\) ,對於 \(i \in (n, n+k]\)

,改成 \(f(i) = \min\begin{cases}\infty & s_{i-k}=\texttt{0} \\ \min\limits_{j\in [i-2k-1,i)}\{f(j)\} + i-k & s_{i-k} = \texttt{1} \end{cases}\)
\(ans=\min\limits_{i\in[n,n+k]} \{f(i)\}\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAX_N (5000000 + 5)
using std::memset;
using std::min;
using std::max;
int T;
int n, k;
char s[MAX_N];
long long f[MAX_N];
int q[MAX_N], l, r;
long long ans;
int main() {
	scanf("%d", &T);
	while (T--) {
		memset(f, 0x3f, sizeof f);
		f[0] = 0;
		l = r = 1;
		q[l] = 0;
		scanf("%d%d%s", &n, &k, s + 1);
		for (int i = 1; i <= n; ++i) {
			if (q[l] + k + k + 1 < i) ++l;
			if (i - k > 0 && s[i - k] == '1') f[i] = f[q[l]] + i - k;
			if (s[i] == '0') f[i] = min(f[i], f[i - 1] + i);
			if (i - k - 1 > 0) {
				while (l <= r && f[q[r]] >= f[i - k - 1]) --r;
				q[++r] = i - k - 1;
			}
		}
		ans = f[n];
		for (int i = n + 1; i <= n + k; ++i) {
			if (q[l] + k + k + 1 < i) ++l;
			if (i - k > 0 && s[i - k] == '1') ans = min(ans, f[q[l]] + i - k);
			if (i - k - 1 > 0) {
				while (l <= r && f[q[r]] >= f[i - k - 1]) --r;
				q[++r] = i - k - 1;
			}
		}
		printf("%lld\n", ans);
	} 
	return 0;
}

其實不用單調佇列,設 \(f(i)\) 表示 \([1,i]\) 被凍住,\((i,n]\) 未知的最少次數,考慮 \(f(i)\) 的單調性,對於每個 \(f(i)\) ,設 \(p_i\) 為左邊距離最遠且可覆蓋到 \(i\) 的禁咒的位置,直接取 \(f(p_i-k-1)\) 即可。
\(f(i) = \min \begin{cases} f(i-1) + i & \\ f(\min\{0, p_i - k - 1\}) + p_i & p_i \text{ exists.} \end{cases}\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAX_N (5000000 + 5)
using std::memset;
using std::min;
using std::max;
int T;
int n, k;
char s[MAX_N];
long long f[MAX_N];
int p[MAX_N];
long long ans;
int main() {
	scanf("%d", &T);
	while (T--) {
		memset(p, 0, sizeof p);
		scanf("%d%d%s", &n, &k, s + 1);
		int tmp = 1;
		for (int i = 1; i <= n; ++i) {
			if (s[i] == '0') continue;
			if (tmp < i) tmp = i;
			while (tmp <= n && tmp <= i + k) p[tmp++] = i;
		}
		for (int i = 1; i <= n; ++i) {
			f[i] = f[i - 1] + i;
			if (p[i]) f[i] = min(f[i], f[max(0, p[i] - k - 1)] + p[i]);
		}
		printf("%lld\n", f[n]);
	} 
	return 0;
}