感觉很是妙啊……这题数次误入歧途...最开始想的二维dp,单调队列优化;无果,卒。于是没忍住看了下标签:暴力枚举?搜索?于是开始想记忆化搜索。以为会有什么很强的剪枝之类的;30分,卒。最后终于回到正道上:50 0000的数据,只可能有O(n) & O(nlogn)两种复杂度吧?在这样的思想+标签线段树的指引下,总算是走向了光明。
暴力,正解的开端。首先考虑最开始的二维dp,转移方程为:dp[i] = min(dp[k] + 1) (k ∈ 1 ~ i - 1) , 且 i ~ k + 1为合法区间。大部分的时间消耗都在于枚举找最值+判断是否合法上。对于这部分的优化,我们先考虑一段合法的区间:要么相差 <= m, 要么都是一个人的粉丝。第二种情况明显特判就行,可以做到O(n), 暂时撇去不谈。再看第一种情况并列出式子:1. abs (a[i] - a[j - 1] - b[i] + b[j - 1]) <= m; 2. a[i] - b[i] - m <= a[j - 1] - b[j - 1] <= a[i] - b[i] + m. 到这里发现,可以用线段树维护区间的最值,将线段树建成 a[i] - b[i]的权值线段树,每次查询在满足条件的范围内的dp最小值就好了。注意要防止爆负数,加上一个大一点的数。
#include <bits/stdc++.h> using namespace std; #define INF 1061109567 #define maxn 600000 #define ADD 10000 int n, m, a[maxn], c[maxn], cont = INF, b[maxn], dp[maxn], ans = INF; int N = 1000000; struct tree { int l, r, num; }T[maxn * 4]; int read() { int x = 0; char c; c = getchar(); while(c < ‘0‘ || c > ‘9‘) c = getchar(); while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar(); return x; } void Build(int p, int l, int r) { T[p].l = l, T[p].r = r, T[p].num = INF; if(l == r) return; int mid = (l + r) >> 1; Build(p << 1, l, mid), Build(p << 1 | 1, mid + 1, r); } void Getmin(int &x, int y) { if(x > y) x = y; } void update(int p, int x, int num) { if(T[p].l == T[p].r) { Getmin(T[p].num, num); return; } int mid = (T[p].l + T[p].r) >> 1; if(x <= mid) update(p << 1, x, num); else update(p << 1 | 1, x, num); T[p].num = min(T[p << 1].num, T[p << 1 | 1].num); } int query(int p, int l, int r) { int L = T[p].l, R = T[p].r; if(R < l || L > r) return INF; if(l <= L && r >= R) return T[p].num; return min(query(p << 1, l, r), query(p << 1 | 1, l, r)); } int main() { n = read(), m = read(); memset(dp, 0x3f3f3f, sizeof(dp)); Build(1, 1, N); for(int i = 1; i <= n; i ++) { c[i] = read(); a[i] = a[i - 1] + (c[i] == 1); b[i] = b[i - 1] + (c[i] == 2); } dp[0] = 0; update(1, ADD, dp[0]); for(int i = 1; i <= n; i ++) { bool flag = false; if(c[i] == c[i - 1]) dp[i] = cont + 1; else flag = true; Getmin(dp[i], dp[i - 1] + 1); int tem = query(1, a[i] - b[i] - m + ADD, a[i] - b[i] + m + ADD); Getmin(dp[i], tem + 1); if(flag) cont = min(dp[i - 1], dp[i]); else Getmin(cont, dp[i]); update(1, a[i] - b[i] + ADD, dp[i]); } printf("%d\n", dp[n]); return 0; }