// 2021.2.18
// 力扣
// 1. 对于当前位置为 0 果断反转
// 2. 对于当前位置为 1 反转两次没有任何意义
// 因此,原题目变成从左向右依次消除当前位置的 0 需要多少次?
// 目前,先采用 树状数组或线段树维护一次区间操作,提交后发现排名并不优,存在更优解法
// 1. 线段树
func minKBitFlips(A []int, K int) int {
n, ans := len(A), 0
add := make([]int, (n<<2)+10)
build(0, n-1, 1, add, A)
for i := 0; i < n; i++ {
ai := query(i, 0, n-1, 1, add)
if ai%2 == 0 && i + K - 1 < n {
ans++
update(i, i+K-1, 0, n-1, 1, 1, add)
}
}
ok := true
for i := 0; i < n; i++ {
if query(i, 0, n-1, 1, add) % 2 == 0 {
ok = false
break
}
}
if !ok {
return -1
}
return ans
}
func build(l, r, idx int, add []int, A []int) {
if l == r {
add[idx] = A[l]
return
}
mid := (l + r) >> 1
build(l, mid, idx<<1, add, A)
build(mid+1, r, (idx<<1)|1, add, A)
}
func push_down(idx int, add []int) {
if add[idx] > 0 {
add[idx<<1] += add[idx]
add[(idx<<1)|1] += add[idx]
add[idx] = 0
}
}
func query(p, l, r, idx int, add []int) int {
if l == r {
return add[idx]
}
push_down(idx, add)
mid := (l+r)>>1
if p <= mid {
return query(p, l, mid, idx<<1, add)
}
return query(p, mid+1, r, (idx<<1)|1, add)
}
func update(L, R, l, r, c, idx int, add []int) {
if L <= l && r <= R {
add[idx] += c
return
}
push_down(idx, add)
mid := (l + r) >> 1
if L <= mid {
update(L, R, l, mid, c, idx<<1, add)
}
if R > mid {
update(L, R, mid+1, r, c, (idx<<1)|1, add)
}
}
// 2. 树状数组
// 树状数组 + 差分数组性质搞搞即可
// 3. 更优解法
// 二分
func minKBitFlips(A []int, K int) int {
n, ans := len(A), 0
add := make([]int, 0, n)
var calcDelta = func(idx int) int {
return len(add) - sort.SearchInts(add, idx)
}
for i := 0; i < n; i++ {
A[i] += calcDelta(i)
if A[i]%2 == 0 && i+K-1 < n {
ans++
add = append(add, i+K-1)
A[i]++
}
}
ok := true
for i := 0; i < n; i++ {
if A[i]%2 == 0 {
ok = false
break
}
}
if !ok {
return -1
}
return ans
}
// 二分再优化下: 滑动窗口
func minKBitFlips(A []int, K int) int {
n, ans, ok := len(A), 0, true
add, curIdx := make([]int, 0, n), 0
for i := 0; i < n; i++ {
for curIdx < len(add) && add[curIdx] < i {
curIdx++
}
A[i] += len(add) - curIdx
if A[i]&1 == 0 && i+K-1 < n {
ans++
add = append(add, i+K-1)
A[i]++
}
if A[i]&1 == 0 {
ok = false
break
}
}
if !ok {
return -1
}
return ans
}
// 利用一个方法:给定一系列操作区间 [l, r],求解经过一系列操作后,最终每个索引 idx 的最终值
// 维护一个增量数组 add,对于每次 [l,r] 操作 add[l]+=c, add[r]-=c,最终 add 数组前缀和即为位置 i
// 处的增量
// O(n) 解法
func minKBitFlips(A []int, K int) int {
n, ans, ok := len(A), 0, true
add := make([]int, n+1)
for i := 0; i < n; i++ {
if i-1 >= 0 {
add[i] += add[i-1]
}
A[i] += add[i]
if A[i]&1 == 0 && i+K-1 < n {
ans++
add[i]++
add[i+K]--
A[i]++
}
if A[i]&1 == 0 {
ok = false
break
}
}
if !ok {
return -1
}
return ans
}