2020 寒假记录(二)

知识点:二分,区间搜索

数的范围

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;

}

1470173-20200114162502476-1806893205.jpg

#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;
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值