小红的01串操作(C++/Py/Java /Js/Go)题解
阿里算法岗 0523笔试 第三题
题目内容
小红定义一个仅由字符 '000' 和 '111' 组成的字符串(简称 010101 串)的权值为:
- 仅由 '111' 构成的非空连续子串的数量。
现给定一长为 nnn 的 010101 串,小红会对其进行 qqq 次操作,每次操作选取 010101 串的一位将其反置(若原字符为 '111',反置后为 '000';若原字符为 '000',反置后为 '111')。
你需要计算每次操作后 010101 串的权值是多少。
【名词解释】 连续子串:从原字符串中,连续的选择一段字符(可以全选、可以不选)得到的新字符串。
输入描述
第一行输入两个整数 n,q (1≤n≤2×105,1≤q≤2×105)n,q\ (1 \le n \le 2 \times 10^5, 1 \le q \le 2 \times 10^5)n,q (1≤n≤2×105,1≤q≤2×105)。
第二行输入一个长为 nnn 的 010101 串。
之后 qqq 行,每行输入一个整数 k (1≤k≤n)k\ (1 \le k \le n)k (1≤k≤n) 代表一次操作,将 010101 串的第 kkk 位反置。
输出描述
输出 qqq 行,每行包含一个整数,代表此次操作后 010101 串的权值。
样例1
输入
5 2
10010
2
3
输出
4
10
题解
思路
逻辑分析
- 对于长度为
len的连续1子串,可以贡献的权重为len * (len + 1) / 2。 - 初始可以预处理每个位置所处连续子串的长度,至于
1 0反转本质上是1连续子串的中断和连接。利用1的特性是可以计算出中断或连接之后增加/减少的权重。 - 按照2逻辑计算即可。
C++
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int n,q;
cin >> n >> q;
string s;
cin >> s;
// i所处连续1数量
vector<int> oneLen(n, 0);
long weight = 0;
// 计算初始权值
for (int i = 0; i < n;) {
if (s[i] == '1') {
int j = i;
while (j < n && s[j] == '1') {
j++;
}
// 连续1的长度
int oneLength = j - i;
weight += (long)oneLength * (oneLength + 1) /2;
for (int k = i; k < j; k++) {
oneLen[k] = oneLength;
}
i = j;
} else {
i++;
}
}
while (q--) {
int pos;
cin >> pos;
// 转换为0-base
pos -= 1;
int leftOne = (pos > 0) ? oneLen[pos - 1] : 0;
int rightOne = (pos < n - 1) ? oneLen[pos+1] : 0;
if (s[pos] == '0') {
// 只会新增一个1子串
if (leftOne == 0 && rightOne == 0) {
weight += 1;
oneLen[pos] = 1;
} else if (leftOne > 0 && rightOne > 0) {
// 先减去左右贡献再加上 连接后的 贡献
weight -= ((long) leftOne * (leftOne + 1) / 2 + (long) rightOne * (rightOne + 1) / 2);
int newOneLen = leftOne + rightOne + 1;
weight += (long)newOneLen * (newOneLen + 1) / 2;
for (int i = pos - leftOne; i <= pos + rightOne; i++) {
oneLen[i] = newOneLen;
}
// 只有单边存在1
} else {
int oldOneLen = max(leftOne, rightOne);
int newOneLen = oldOneLen + 1;
weight += newOneLen;
if (leftOne > 0) {
for (int i = pos - leftOne; i <= pos; i++) {
oneLen[i] = newOneLen;
}
} else {
for (int i = pos; i <= pos + rightOne; i++) {
oneLen[i] = newOneLen;
}
}
}
s[pos] = '1';
} else {
if (leftOne == 0 && rightOne == 0) {
weight -= 1;
oneLen[pos] = 0;
// 中断连续1
} else if (leftOne > 0 && rightOne > 0) {
weight -= (long)leftOne * (leftOne + 1) / 2;
// 左侧1终止 右侧终止1位置
int leftPos = pos, rightPos = pos;
while (leftPos - 1 > 0 && s[leftPos - 1] == '1') {
leftPos--;
}
while (rightPos + 1 < n && s[rightPos + 1] =='1') {
rightPos++;
}
int newRighOneLen = rightPos - pos;
int newLeftOneLen = pos - leftPos;
weight += (long)newLeftOneLen * (newLeftOneLen + 1) / 2 + (long)newRighOneLen * (newRighOneLen + 1) / 2;
for(int i = leftPos; i< pos; i++) {
oneLen[i] = newLeftOneLen;
}
for (int i = pos + 1; i <= rightPos; i++) {
oneLen[i] = newRighOneLen;
}
oneLen[pos] = 0;
// 位于连续1的首或者尾
} else {
weight -= max(leftOne, rightOne);
int newOneLen = max(leftOne, rightOne) - 1;
if (leftOne > 1) {
for (int i = pos - newOneLen; i < pos; i++) {
oneLen[i] = newOneLen;
}
} else {
for (int i = pos + 1; i <= pos + newOneLen; i++) {
oneLen[i] = newOneLen;
}
}
oneLen[pos] = 0;
}
s[pos] = '0';
}
cout << weight << endl;
}
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] first = br.readLine().split(" ");
int n = Integer.parseInt(first[0]);
int q = Integer.parseInt(first[1]);
StringBuilder s = new StringBuilder(br.readLine());
// i所处连续1数量
int[] oneLen = new int[n];
long weight = 0;
// 计算初始权值
for (int i = 0; i < n; ) {
if (s.charAt(i) == '1') {
int j = i;
while (j < n && s.charAt(j) == '1') {
j++;
}
// 连续1的长度
int oneLength = j - i;
weight += (long) oneLength * (oneLength + 1) / 2;
for (int k = i; k < j; k++) {
oneLen[k] = oneLength;
}
i = j;
} else {
i++;
}
}
StringBuilder ans = new StringBuilder();
while (q-- > 0) {
int pos = Integer.parseInt(br.readLine());
// 转换为0-base
pos--;
int leftOne = (pos > 0) ? oneLen[pos - 1] : 0;
int rightOne = (pos < n - 1) ? oneLen[pos + 1] : 0;
if (s.charAt(pos) == '0') {
// 只会新增一个1子串
if (leftOne == 0 && rightOne == 0) {
weight += 1;
oneLen[pos] = 1;
} else if (leftOne > 0 && rightOne > 0) {
// 先减去左右贡献再加上 连接后的 贡献
weight -= (long) leftOne * (leftOne + 1) / 2;
weight -= (long) rightOne * (rightOne + 1) / 2;
int newOneLen = leftOne + rightOne + 1;
weight += (long) newOneLen * (newOneLen + 1) / 2;
for (int i = pos - leftOne; i <= pos + rightOne; i++) {
oneLen[i] = newOneLen;
}
// 只有单边存在1
} else {
int oldOneLen = Math.max(leftOne, rightOne);
int newOneLen = oldOneLen + 1;
weight += newOneLen;
if (leftOne > 0) {
for (int i = pos - leftOne; i <= pos; i++) {
oneLen[i] = newOneLen;
}
} else {
for (int i = pos; i <= pos + rightOne; i++) {
oneLen[i] = newOneLen;
}
}
}
s.setCharAt(pos, '1');
} else {
if (leftOne == 0 && rightOne == 0) {
weight -= 1;
oneLen[pos] = 0;
// 中断连续1
} else if (leftOne > 0 && rightOne > 0) {
weight -= (long) oneLen[pos] * (oneLen[pos] + 1) / 2;
// 左侧1终止 右侧终止1位置
int leftPos = pos;
int rightPos = pos;
while (leftPos - 1 >= 0 && s.charAt(leftPos - 1) == '1') {
leftPos--;
}
while (rightPos + 1 < n && s.charAt(rightPos + 1) == '1') {
rightPos++;
}
int newRightOneLen = rightPos - pos;
int newLeftOneLen = pos - leftPos;
weight += (long) newLeftOneLen * (newLeftOneLen + 1) / 2;
weight += (long) newRightOneLen * (newRightOneLen + 1) / 2;
for (int i = leftPos; i < pos; i++) {
oneLen[i] = newLeftOneLen;
}
for (int i = pos + 1; i <= rightPos; i++) {
oneLen[i] = newRightOneLen;
}
oneLen[pos] = 0;
// 位于连续1的首或者尾
} else {
weight -= Math.max(leftOne, rightOne);
int newOneLen = Math.max(leftOne, rightOne) - 1;
if (leftOne > 1) {
for (int i = pos - newOneLen; i < pos; i++) {
oneLen[i] = newOneLen;
}
} else {
for (int i = pos + 1; i <= pos + newOneLen; i++) {
oneLen[i] = newOneLen;
}
}
oneLen[pos] = 0;
}
s.setCharAt(pos, '0');
}
ans.append(weight).append('\n');
}
System.out.print(ans);
}
}
python
python
n, q = map(int, input().split())
s = list(input())
# i所处连续1数量
one_len = [0] * n
weight = 0
# 计算初始权值
i = 0
while i < n:
if s[i] == '1':
j = i
while j < n and s[j] == '1':
j += 1
# 连续1的长度
one_length = j - i
weight += one_length * (one_length + 1) // 2
for k in range(i, j):
one_len[k] = one_length
i = j
else:
i += 1
for _ in range(q):
pos = int(input())
# 转换为0-base
pos -= 1
left_one = one_len[pos - 1] if pos > 0 else 0
right_one = one_len[pos + 1] if pos < n - 1 else 0
if s[pos] == '0':
# 只会新增一个1子串
if left_one == 0 and right_one == 0:
weight += 1
one_len[pos] = 1
elif left_one > 0 and right_one > 0:
# 先减去左右贡献再加上 连接后的 贡献
weight -= left_one * (left_one + 1) // 2
weight -= right_one * (right_one + 1) // 2
new_one_len = left_one + right_one + 1
weight += new_one_len * (new_one_len + 1) // 2
for i in range(pos - left_one, pos + right_one + 1):
one_len[i] = new_one_len
# 只有单边存在1
else:
old_one_len = max(left_one, right_one)
new_one_len = old_one_len + 1
weight += new_one_len
if left_one > 0:
for i in range(pos - left_one, pos + 1):
one_len[i] = new_one_len
else:
for i in range(pos, pos + right_one + 1):
one_len[i] = new_one_len
s[pos] = '1'
else:
if left_one == 0 and right_one == 0:
weight -= 1
one_len[pos] = 0
# 中断连续1
elif left_one > 0 and right_one > 0:
weight -= one_len[pos] * (one_len[pos] + 1) // 2
# 左侧1终止 右侧终止1位置
left_pos = pos
right_pos = pos
while left_pos - 1 >= 0 and s[left_pos - 1] == '1':
left_pos -= 1
while right_pos + 1 < n and s[right_pos + 1] == '1':
right_pos += 1
new_right_one_len = right_pos - pos
new_left_one_len = pos - left_pos
weight += new_left_one_len * (new_left_one_len + 1) // 2
weight += new_right_one_len * (new_right_one_len + 1) // 2
for i in range(left_pos, pos):
one_len[i] = new_left_one_len
for i in range(pos + 1, right_pos + 1):
one_len[i] = new_right_one_len
one_len[pos] = 0
# 位于连续1的首或者尾
else:
weight -= max(left_one, right_one)
new_one_len = max(left_one, right_one) - 1
if left_one > 1:
for i in range(pos - new_one_len, pos):
one_len[i] = new_one_len
else:
for i in range(pos + 1, pos + new_one_len + 1):
one_len[i] = new_one_len
one_len[pos] = 0
s[pos] = '0'
print(weight)
javascript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const input = [];
rl.on('line', line => {
input.push(line);
});
rl.on('close', () => {
let idx = 0;
const [n, q] = input[idx++].split(" ").map(Number);
const s = input[idx++].split("");
// i所处连续1数量
const oneLen = new Array(n).fill(0);
let weight = 0;
// 计算初始权值
for (let i = 0; i < n;) {
if (s[i] === '1') {
let j = i;
while (j < n && s[j] === '1') {
j++;
}
// 连续1的长度
const oneLength = j - i;
weight += oneLength * (oneLength + 1) / 2;
for (let k = i; k < j; k++) {
oneLen[k] = oneLength;
}
i = j;
} else {
i++;
}
}
const ans = [];
for (let t = 0; t < q; t++) {
let pos = Number(input[idx++]);
// 转换为0-base
pos--;
const leftOne = (pos > 0) ? oneLen[pos - 1] : 0;
const rightOne = (pos < n - 1) ? oneLen[pos + 1] : 0;
if (s[pos] === '0') {
// 只会新增一个1子串
if (leftOne === 0 && rightOne === 0) {
weight += 1;
oneLen[pos] = 1;
} else if (leftOne > 0 && rightOne > 0) {
// 先减去左右贡献再加上 连接后的 贡献
weight -= leftOne * (leftOne + 1) / 2;
weight -= rightOne * (rightOne + 1) / 2;
const newOneLen = leftOne + rightOne + 1;
weight += newOneLen * (newOneLen + 1) / 2;
for (let i = pos - leftOne; i <= pos + rightOne; i++) {
oneLen[i] = newOneLen;
}
// 只有单边存在1
} else {
const oldOneLen = Math.max(leftOne, rightOne);
const newOneLen = oldOneLen + 1;
weight += newOneLen;
if (leftOne > 0) {
for (let i = pos - leftOne; i <= pos; i++) {
oneLen[i] = newOneLen;
}
} else {
for (let i = pos; i <= pos + rightOne; i++) {
oneLen[i] = newOneLen;
}
}
}
s[pos] = '1';
} else {
if (leftOne === 0 && rightOne === 0) {
weight -= 1;
oneLen[pos] = 0;
// 中断连续1
} else if (leftOne > 0 && rightOne > 0) {
weight -= oneLen[pos] * (oneLen[pos] + 1) / 2;
// 左侧1终止 右侧终止1位置
let leftPos = pos;
let rightPos = pos;
while (leftPos - 1 >= 0 && s[leftPos - 1] === '1') {
leftPos--;
}
while (rightPos + 1 < n && s[rightPos + 1] === '1') {
rightPos++;
}
const newRightOneLen = rightPos - pos;
const newLeftOneLen = pos - leftPos;
weight += newLeftOneLen * (newLeftOneLen + 1) / 2;
weight += newRightOneLen * (newRightOneLen + 1) / 2;
for (let i = leftPos; i < pos; i++) {
oneLen[i] = newLeftOneLen;
}
for (let i = pos + 1; i <= rightPos; i++) {
oneLen[i] = newRightOneLen;
}
oneLen[pos] = 0;
// 位于连续1的首或者尾
} else {
weight -= Math.max(leftOne, rightOne);
const newOneLen = Math.max(leftOne, rightOne) - 1;
if (leftOne > 1) {
for (let i = pos - newOneLen; i < pos; i++) {
oneLen[i] = newOneLen;
}
} else {
for (let i = pos + 1; i <= pos + newOneLen; i++) {
oneLen[i] = newOneLen;
}
}
oneLen[pos] = 0;
}
s[pos] = '0';
}
ans.push(weight);
}
console.log(ans.join("\n"));
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
var n, q int
fmt.Fscan(in, &n, &q)
var s string
fmt.Fscan(in, &s)
str := []byte(s)
// i所处连续1数量
oneLen := make([]int, n)
var weight int64 = 0
// 计算初始权值
for i := 0; i < n; {
if str[i] == '1' {
j := i
for j < n && str[j] == '1' {
j++
}
// 连续1的长度
oneLength := j - i
weight += int64(oneLength*(oneLength+1)) / 2
for k := i; k < j; k++ {
oneLen[k] = oneLength
}
i = j
} else {
i++
}
}
for ; q > 0; q-- {
var pos int
fmt.Fscan(in, &pos)
// 转换为0-base
pos--
leftOne := 0
if pos > 0 {
leftOne = oneLen[pos-1]
}
rightOne := 0
if pos < n-1 {
rightOne = oneLen[pos+1]
}
if str[pos] == '0' {
// 只会新增一个1子串
if leftOne == 0 && rightOne == 0 {
weight++
oneLen[pos] = 1
} else if leftOne > 0 && rightOne > 0 {
// 先减去左右贡献再加上 连接后的 贡献
weight -= int64(leftOne*(leftOne+1))/2 + int64(rightOne*(rightOne+1))/2
newOneLen := leftOne + rightOne + 1
weight += int64(newOneLen*(newOneLen+1)) / 2
for i := pos - leftOne; i <= pos+rightOne; i++ {
oneLen[i] = newOneLen
}
// 只有单边存在1
} else {
oldOneLen := leftOne
if rightOne > oldOneLen {
oldOneLen = rightOne
}
newOneLen := oldOneLen + 1
weight += int64(newOneLen)
if leftOne > 0 {
for i := pos - leftOne; i <= pos; i++ {
oneLen[i] = newOneLen
}
} else {
for i := pos; i <= pos+rightOne; i++ {
oneLen[i] = newOneLen
}
}
}
str[pos] = '1'
} else {
if leftOne == 0 && rightOne == 0 {
weight--
oneLen[pos] = 0
// 中断连续1
} else if leftOne > 0 && rightOne > 0 {
weight -= int64(oneLen[pos]*(oneLen[pos]+1)) / 2
// 左侧1终止 右侧终止1位置
leftPos := pos
rightPos := pos
for leftPos-1 >= 0 && str[leftPos-1] == '1' {
leftPos--
}
for rightPos+1 < n && str[rightPos+1] == '1' {
rightPos++
}
newRightOneLen := rightPos - pos
newLeftOneLen := pos - leftPos
weight += int64(newLeftOneLen*(newLeftOneLen+1))/2 +
int64(newRightOneLen*(newRightOneLen+1))/2
for i := leftPos; i < pos; i++ {
oneLen[i] = newLeftOneLen
}
for i := pos + 1; i <= rightPos; i++ {
oneLen[i] = newRightOneLen
}
oneLen[pos] = 0
// 位于连续1的首或者尾
} else {
old := leftOne
if rightOne > old {
old = rightOne
}
weight -= int64(old)
newOneLen := old - 1
if leftOne > 1 {
for i := pos - newOneLen; i < pos; i++ {
oneLen[i] = newOneLen
}
} else {
for i := pos + 1; i <= pos+newOneLen; i++ {
oneLen[i] = newOneLen
}
}
oneLen[pos] = 0
}
str[pos] = '0'
}
fmt.Println(weight)
}
}