01字符串
原题链接:http://codeforces.com/problemset/problem/165/C
【题目描述】
一个字符串被称为“01字符串”当且仅当它只包含字符“0”和“1”。
我们从字符串 s 中取出连续的一段组成的非空字符串就是 s 的子串。
比如,"010" 有6个子串:"0", "1", "0", "01", "10", "010"。
一个字符串的两个子串被定义为不同的,当且晋档它们的起始位置或者终止位置不一样就可以了。
给你一个 01字符串 s ,请计算 s 中有多少子串包含了刚好 k 个字符"1"。
【输入格式】
输入的第一行包含一个整数 k (1<=k<=10^6)。
输入的第二行包含一个 01字符串 s, s 的长度不超过 10^6。
【输出格式】
输出一个整数,用于表示 s 的子串中有多少子串包含了刚好 k 个 "1" 。
【样例输入1】
1
1010
【样例输出1】
6
【样例输入2】
2
01010
【样例输出2】
4
【样例输入3】
100
01010
【样例输出3】
0
【样例解释】
在样例1中满足条件的子串有:"1", "1", "10", "01", "10", "010";
在样例2中满足条件的子串有:"101", "0101", "1010", "01010"。
【问题分析】
这道题目存在线性解法,不如双指针。
不过这场比赛主要还是二分专场,所以主要还是讲解二分算法。
首先我们知道,二分问题的求解条件是存在一个单调函数。
所以这里我们需要开一个 sum[] 数组, sum[i] 表示字符串 s[0..i] 中包含了多少个 "1",
基于这个条件,我们就可以进行二分了。
对于以 s[i] 开头的子串,
我们需要二分找到sum[j1]-sum[i-1]>k的最小的元素坐标j1,
以及sum[j2]-sum[i-1]>=k的最大的元素坐标j2(当然如果j2>i,那么我们要将j2赋为i)。
那么以 s[i] 开头包含 k 个 "1" 的子串数量一共有 j1-j2 个。
实现代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn = 1000100; int k, n, sum[maxn]; string s; long long ans; int get1(int num) { // 大于num的最小元素的坐标,没有则返回n+1 int L = 1, R = n, res = n+1; while (L <= R) { int mid = (L + R) / 2; if (sum[mid] > num) { res = mid; R = mid - 1; } else L = mid + 1; } return res; } int get2(int num) { // 大于等于num的最小元素的坐标,没有则返回n+1 int L = 1, R = n, res = n+1; while (L <= R) { int mid = (L + R) / 2; if (sum[mid] >= num) { res = mid; R = mid - 1; } else L = mid + 1; } return res; } int main() { cin >> k >> s; n = s.length(); for (int i = 1; i <= n; i ++) sum[i] = sum[i-1] + (s[i-1] == ‘1‘); for (int i = 1; i <= n; i ++) { int j1 = get1(k+sum[i-1]); int j2 = get2(k+sum[i-1]); if (j2 < i) j2 = i; if (j2 < i) continue; ans += j1 - j2; } cout << ans << endl; return 0; }
原文地址:https://www.cnblogs.com/ocac/p/11156520.html