知识点:二分,区间搜索
数的范围
https://www.acwing.com/problem/content/791/
二分
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int arr[100010];
int main() {
int n, q, k;
cin >> n >> q;
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
while (q--) {
scanf("%d", &k);
int L = 0, R = n - 1;
while (L < R) {
int mid = (L + R) / 2;
//arr[mid]==k,左边界一定在L和mid之间
if (arr[mid] == k)R = mid;
else if (arr[mid] > k)R = mid - 1;
else L = mid + 1;
}
if (arr[L] != k) {
cout << -1 << ' ' << -1 << endl; continue;
}
cout << L << ' ';
L = 0; R = n - 1;
while (L < R) {
//6 1
//1 2 2 3 3 4
//3
//找3的右边界时,L=4,R=5,如果mid=(L+R)/2,就会出现死循环,这里Mid的取值要稍作改变
int mid = (L + R + 1) / 2;
if (arr[mid] == k)L = mid;
else if (arr[mid] > k)R = mid - 1;
else L = mid + 1;
}
cout << R << endl;
}
return 0;
}
数的三次方根
https://www.acwing.com/problem/content/792/
二分
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
int main() {
double x;
cin >> x;
double l = -10000, r = 10000;
while (true) {
double mid = (l + r) / 2;
if (fabs(mid * mid * mid - x) < 1e-6) {
printf("%.6lf\n", mid);
break;
}
else if (mid * mid * mid > x)
r = mid;
else
l = mid;
}
return 0;
}
前缀和
https://www.acwing.com/problem/content/description/797/
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
int arr[100005];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
arr[i] += arr[i - 1];
}
int l, r;
while (m--) {
scanf("%d%d", &l, &r);
cout << arr[r] - arr[l - 1] << endl;
}
return 0;
}
子矩阵和
https://www.acwing.com/problem/content/798/
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
int maze[1005][1005];
int sum[1005][1005];//sum[i][j]表示从0,0到i,j所构成的矩形范围的矩阵和
int main() {
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &maze[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
sum[i][j] += sum[i][j - 1] + sum[i - 1][j] + maze[i][j] - sum[i - 1][j - 1];
}
}
int x1, x2, y1, y2;
while (q--) {
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
int ans = sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
printf("%d\n", ans);
}
return 0;
}
四平方和问题
https://www.acwing.com/problem/content/1223/
暴力枚举,死
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int N;
int a, b, c, d;
//4653056
int main() {
cin >> N;
for (a = 0; a * a < N; a++) {
int A = a * a;
for (b = a; A + b * b < N; b++) {
int B = b * b;
for (c = b; c * c + B + A < N; c++) {
int C = c * c;
int D = N - A - B - C;
int d = sqrt(D);
if (d * d == D) {
cout << a << ' ' << b << ' ' << c << ' ' << d << endl;
return 0;
}
}
}
}
return 0;
}
分析:
\(N<=5\times 10^6\),a,b,c,d的范围也就是\(sqrt(N)\approx 2236\) ,从数据上看枚举三个数会超时,也确实超时了,只枚举前两个数,把 \(c^2+d^2\) 的值先存起来,枚举 a 和 b 判断 \(N-a*a-b*b\) 的值是否在在之前出现过。
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int N;
int a, b, c, d;
bool flag[10000000];
int main() {
cin >> N;
int len=sqrt(N)+1;
for (int i = 0; i < 2237; i++) {
for (int j = 0; j < 2237; j++) {
flag[i * i + j * j] = true;
}
}
for (a = 0; a * a < N; a++) {
int A = a * a;
for (b = a; A + b * b < N; b++) {
int B = b * b;
int temp = N - A - B;
if (flag[temp]) {
for (c = b; c * c < temp; c++) {
int D = temp - c * c;
d = sqrt(D);
if (d * d == D) {
cout << a << ' ' << b << ' ' << c << ' ' << d << endl;
return 0;
}
}
}
}
}
return 0;
}
机器人跳跃问题
https://www.acwing.com/problem/content/732/
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
int H[100005];
int main() {
int N, mmax = 0;
cin >> N;
for (int i = 1; i <= N; i++) {
scanf("%d", &H[i]);
mmax = max(H[i], mmax);
}
int L = 0, R = mmax;
while (L < R) {
int E = (L + R) / 2, mid = E, k = 0;
while (E >= 0 && E < mmax && k < N) {
E = 2 * E - H[k + 1];
k++;
}
if (E >= 0)R = mid;
else L = mid + 1;
}
printf("%d\n", L);
return 0;
}
小结:
问题具有二段性,则可以用二分,也就是形如:如果 \(E_0\) 满足要求,可以判断所有 \(E_i>E_0\)都(不)满足要求,或者 \(E_i<E_0\)都(不)满足要求。划定一个初始范围 \(L\) 和 \(R\) ,根据题目计算一个 \(mid\) 值,判断第 \(mid\) 种情况是否满足,不断缩小范围,保证答案一定在范围内。
然后划分边界,要注意一点,当存在
if (...)
L = Mid;
应当像下面一样进行划分边界,否则比如 L=4 , R=5,满足上面的 if(...)
就会出现死循环
//int Mid = (L + R) / 2;
int Mid = (L + R + 1) / 2;
分巧克力
https://www.acwing.com/problem/content/1229/
问题具有二段性,当边长为 \(k_0\) (不)满足时,所有(大)小于 \(k_0\) 的都(不)满足。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
typedef pair<int, int>PP;
#define y second
#define x first
PP H[100005];
int main() {
int N, K, mmax = 0;
cin >> N >> K;
for (int i = 0; i < N; i++) {
scanf("%d%d", &H[i].x, &H[i].y);
mmax = max(max(H[i].x, H[i].y), mmax);
}
int L = 1, R = mmax;
while (L < R) {
int M = (L + R + 1) / 2;
int cnt = 0;
for (int i = 0; i < N; i++) {
cnt += (H[i].x / M) * (H[i].y / M);
}
if (cnt >= K) {
L = M;
}
else {
R = M - 1;
}
}
cout << L << endl;
return 0;
}
激光炸弹
https://www.acwing.com/problem/content/101/
子矩阵和的应用,就要注意一点,坐标x,y的起始值和定义的二维数组的起始索引要对应上,定义的数组是从(1,1)开始存的,卡了好一会。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
int sum[5005][5005];
int main() {
int N, R, x, y;
cin >> N >> R;
for (int i = 0; i < N; i++) {
scanf("%d%d", &x, &y);
scanf("%d", &sum[x + 1][y + 1]);
}
for (int i = 1; i <= 5000; i++) {
for (int j = 1; j <= 5000; j++) {
sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
}
}
if (R > 5000) {
printf("%d\n", sum[5000][5000]);
return 0;
}
int mmax = 0;
// 枚举所有子矩阵和
for (int i = R; i <= 5000; i++) {
for (int j = R; j <= 5000; j++) {
int temp = sum[i][j] - sum[i - R][j] - sum[i][j - R] + sum[i - R][j - R];
mmax = max(mmax, temp);
}
}
cout << mmax << endl;
return 0;
}
K倍区间
\(10^5\),暴力枚举肯定是不行的,不过注意到了一个点,数据只有正数, \(sum[i]\) (前 \(i\) 项和)是单调递增的,想要尝试二分,发现不太行,写成了分治,然后超时。。
![1578990268.078959](C:\Users\qmyc\Documents\Tencent Files\407182090\FileRecv\MobileFile\1578990268.078959.jpg)#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
int N, K, res;
int arr[100005];
int sum[100005];
void divide(int start, int left, int right) {
if (left == right) {
if ((sum[left] - sum[start - 1]) % K == 0) {
res++;
}
return;
}
if (left < right) {
int mid = (left + right) / 2;
if (sum[mid] - sum[start - 1] < K) {
divide(start, mid + 1, right);
}
else {
if ((sum[mid] - sum[start - 1]) % K == 0) {
res++;
}
divide(start, left, mid - 1);
divide(start, mid + 1, right);
}
}
}
int main() {
cin >> N >> K;
for (int i = 1; i <= N; i++) {
scanf("%d", &arr[i]);
sum[i] += arr[i] + sum[i - 1];
}
//枚举起点
for (int i = 1; i <= N; i++) {
divide(i, i, N);
//寻找终点
}
cout << res << endl;
return 0;
}
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, k;
int s[N], cnt[N];
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%lld", &s[i]);
s[i] += s[i - 1] % k;
}
long long res = 0;
cnt[0] = 1;
for (int i = 1; i <= n; i++) {
res += cnt[s[i] % k];
cnt[s[i] % k] ++;
}
printf("%lld\n", res);
return 0;
}