算法學習 二分與前綴和

例題1 數的範圍

題目

給定一個按照升序排列的長度爲n的整數數組,以及 q 個查詢。

對於每個查詢,返回一個元素k的起始位置和終止位置(位置從0開始計數)。

如果數組中不存在該元素,則返回“-1 -1”。

輸入格式

第一行包含整數n和q,表示數組長度和詢問個數。

第二行包含n個整數(均在1~10000範圍內),表示完整數組。

接下來q行,每行包含一個整數k,表示一個詢問元素。

輸出格式

共q行,每行包含兩個整數,表示所求元素的起始位置和終止位置。

如果數組中不存在該元素,則返回“-1 -1”。

數據範圍

1≤n≤100000
1≤q≤10000
1≤k≤10000

輸入樣例:

6 3
1 2 2 3 3 4
3
4
5

輸出樣例:

3 4
5 5
-1 -1

分析——二分

1.確定區間,確定目標在區間當中
2.找出一個性質,滿足使得整個區間具有二段性
在這裏插入圖片描述

1.離散的情況

第一類

在這裏插入圖片描述
ans在右端點:M就歸於右邊

mid-1,求mid的時候就+1

第一類模板
while(L < R)
{
    M = (L + R + 1) / 2;
    if M 是 綠 
        L = M;  //根據這一步判斷上一步需不需要  +  1   左邊加一,右邊不加
    else
        R = M - 1;
}

第二類

在這裏插入圖片描述
ans在左端點,M就歸於左邊
第二類模板

while(L < R)
{
    M  = (L + R) / 2;  //右邊不用+1
    if M 是 藍色
        R = M;
    else 
        L = M + 1;
}

整數二分分析

在這裏插入圖片描述

代碼分析

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;

const int  N = 100010;
int n,m;  //n數組的長度  m  訪問的個數
int q[N];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 0;i < n;i++) scanf("%d",&q[i]);
    //開始二分
    for(int i = 0;i < m;i++) 
    {
        int x;
        scanf("%d",&x);
        //二分的左端點
        int l = 0,r = n - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(q[mid] >= x)
                r = mid;
            else
                l = mid + 1;
        }
        
        if(q[r] == x)
        {
            //先輸出右邊的值
            cout << r << ' ';
            //左邊的邊界不變,改變右邊的邊界
            r = n - 1;
            //二分的右端點
            while(l < r)
            {
                int mid = (l + r + 1) >> 1;
                if(q[mid] <= x)
                    l = mid;
                else
                    r = mid - 1;
            }
            cout << l << endl;
        }
        else 
            cout << "-1 -1" <<endl;
    }
    return 0;
}

例題2 數的三次方根

題目

給定一個浮點數n,求它的三次方根。

輸入格式

共一行,包含一個浮點數n。

輸出格式

共一行,包含一個浮點數,表示問題的解。

注意,結果保留6位小數。

數據範圍

−10000≤n≤10000

輸入樣例:

1000.00

輸出樣例:

10.000000

代碼

#include<string>
#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    double l = -10000,r = 10000;
    double x;
    cin >> x;
    while(r - l > 1e-8)  //注意這裏把精度多調整兩位
    {
        double mid = (l + r) / 2;
        if(mid * mid *mid <= x)
            l = mid;
        else 
            r = mid;
    }
    printf("%lf\n",r);
    return 0;
}

例題3 前綴和

題目

輸入一個長度爲n的整數序列。

接下來再輸入m個詢問,每個詢問輸入一對l, r。

對於每個詢問,輸出原序列中從第l個數到第r個數的和。

輸入格式

第一行包含兩個整數n和m。

第二行包含n個整數,表示整數數列。

接下來m行,每行包含兩個整數l和r,表示一個詢問的區間範圍。

輸出格式

共m行,每行輸出一個詢問的結果。

數據範圍

1≤l≤r≤n,
1≤n,m≤100000,
−1000≤數列中元素的值≤1000

輸入樣例:

5 3
2 1 3 6 4
1 2
1 3
2 4

輸出樣例:

3
6
10

分析——前綴和

前綴和:求某一個靜態數組的某一段的和
原理:容斥原理
注意:前綴和輸入的時候下標要從1開始,因爲要保證前綴和數組S[0] 爲0
公式
在這裏插入圖片描述

求子矩陣和的公式

在這裏插入圖片描述

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100010;
int n,m;
int s[N],a[N];  //s[N]表示前綴和的值,a[N]表示讀取的數組的值

int main()
{
    scanf("%d%d",&n,&m);  //n是整數數列有多少個,m是下面有多少組值
    for(int i = 1;i <= n;i++)  //讀取整數的數列,注意這裏的下標要從1開始,因爲要保證s[0]爲0
    {
        scanf("%d",&a[i]);
        s[i] = s[i - 1] + a[i];
    }
    
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",s[r] - s[l - 1]);
    }
    return 0;
    
}

例題4 子矩陣的和

題目

輸入一個n行m列的整數矩陣,再輸入q個詢問,每個詢問包含四個整數x1, y1, x2, y2,表示一個子矩陣的左上角座標和右下角座標。

對於每個詢問輸出子矩陣中所有數的和。

輸入格式

第一行包含三個整數n,m,q。

接下來n行,每行包含m個整數,表示整數矩陣。

接下來q行,每行包含四個整數x1, y1, x2, y2,表示一組詢問。

輸出格式

共q行,每行輸出一個詢問的結果。

數據範圍

1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩陣內元素的值≤1000

輸入樣例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

輸出樣例:

17
27
21

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;

const int N = 1010;

int n,m,q;
int a[N][N],s[N][N];
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
        {
            scanf("%d",&a[i][j]);
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
        while(q--)
        {
            int x1,y1,x2,y2;
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            printf("%d\n",s[x2][y2] - s[x1 - 1][y2]  - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
        }
    return 0;
}

例5 機器人跳躍問題

問題

機器人正在玩一個古老的基於DOS的遊戲。

遊戲中有N+1座建築——從0到N編號,從左到右排列。

編號爲0的建築高度爲0個單位,編號爲 i 的建築高度爲H(i)個單位。

起初,機器人在編號爲0的建築處。

每一步,它跳到下一個(右邊)建築。

假設機器人在第k個建築,且它現在的能量值是E,下一步它將跳到第k+1個建築。

如果H(k+1)>E,那麼機器人就失去H(k+1)-E的能量值,否則它將得到E-H(k+1)的能量值。

遊戲目標是到達第N個建築,在這個過程中能量值不能爲負數個單位。

現在的問題是機器人至少以多少能量值開始遊戲,纔可以保證成功完成遊戲?

輸入格式

第一行輸入整數N。

第二行是N個空格分隔的整數,H(1),H(2),…,H(N)代表建築物的高度。

輸出格式

輸出一個整數,表示所需的最少單位的初始能量值上取整後的結果。

數據範圍

1≤N,H(i)≤105,

輸入樣例1:

5
3 4 3 2 4

輸出樣例1:

4

輸入樣例2:

3
4 4 4

輸出樣例2:

4

輸入樣例3:

3
1 6 4

輸出樣例3:

問題分析:

有單調性必然可以用二分來做

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

const int N = 100010;
int n;
int H[N];

bool check(int e)
{
    for(int i = 1;i <= n;i++)
    {
        e = e * 2 - H[i];
        if(e >= 1e5) return true;
        if(e < 0) return false;
    }
    return true;
}

int main()
{
    cin >> n;
    for(int i = 1;i <= n;i++)
        cin >> H[i];
    int l = 0,r = 1e5;
    while(l < r)
    {
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << r;
    return 0;
}

例題6 四平方和

題目

四平方和定理,又稱爲拉格朗日定理:

每個正整數都可以表示爲至多 4 個正整數的平方和。

如果把 0 包括進去,就正好可以表示爲 4 個數的平方和。

比如:

5=02+02+12+22
7=12+12+12+22
對於一個給定的正整數,可能存在多種平方和的表示法。

要求你對 4 個數排序:

0≤a≤b≤c≤d
並對所有的可能表示法按 a,b,c,d 爲聯合主鍵升序排列,最後輸出第一個表示法。

輸入格式

輸入一個正整數 N。

輸出格式

輸出4個非負整數,按從小到大排序,中間用空格分開。

數據範圍

0<N<5∗106

輸入樣例:

5

輸出樣例:

0 0 1 2

題目分析

最多枚舉兩個數 ,如果枚舉三個數的話,三次方會超時。。
方法: 用空間換取時間
在這裏插入圖片描述

代碼

二分寫法
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 2500010;

struct Sum
{
    int s,c,d;    //s存儲  c * c + d * d  
    //實現一個字典序的排列 主要是對sort排序進行一個提前操作
    bool operator< (const Sum &t)const    //重載  <   ???
    {
        if (s != t.s) return s < t.s;
        if (c != t.c) return c < t.c;
        return d < t.d;
    }
}sum[N];

int n,m;   //n 是輸入的數  m是表示存儲的多少的數

int main()
{
    cin >> n;
    //遍歷存儲所有的c和d的情況
    for(int c = 0; c * c <= n;c++)
        for(int d = c;c * c + d * d <= n;d++)
            sum[m++] = {c * c + d * d , c , d};
    sort (sum,sum + m);  //按照字典序進行排序   對c和d的情況
    for(int a = 0;a * a <= n;a++)
        for(int b = a;a * a + b * b <= n;b++)
        {
            int t = n - a * a - b * b;
            int l = 0,r = m - 1;        //在這些情況當中選擇合適的
            //進行二分的操作
            while(l < r)
            {
                int mid = l + r >> 1;
                if(sum[mid].s >= t)  r = mid;
                else l  = mid + 1;
            }
            if(sum[l].s == t)
            {
                printf("%d %d %d %d\n",a,b,sum[l].c,sum[l].d);
                return 0;
            }
        }
    return 0;
    
}
哈希寫法
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<unordered_map>  //哈希表
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;

const int N = 2500010;

int n,m;
unordered_map<int,PII> S;

int main()
{
    cin >> n;
    for(int c = 0;c * c <= n;c++)
        for(int d = 0;c * c + d * d <= n;d++)
        {
            int t  = c * c + d * d;
            if(S.count(t) == 0) S[t] = {c,d};
        }
    for(int a = 0;a * a <= n;a++)
        for(int b = a;a * a + b * b <= n;b++)
        {
            int t  = n - a * a  * b  * b;
            if(S.count(t))
            {
                printf("%d %d %d %d\n",a,b,S[t].x,S[t].y);
                return 0;
            }
        }
    return 0;
}


例題7 分巧克力

題目

兒童節那天有 K 位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友們。

小明一共有 N 塊巧克力,其中第 i 塊是 Hi×Wi 的方格組成的長方形。

爲了公平起見,小明需要從這 N 塊巧克力中切出 K 塊巧克力分給小朋友們。

切出的巧克力需要滿足:

形狀是正方形,邊長是整數
大小相同
例如一塊 6×5 的巧克力可以切出 6 塊 2×2 的巧克力或者 2 塊 3×3 的巧克力。

當然小朋友們都希望得到的巧克力儘可能大,你能幫小明計算出最大的邊長是多少麼?

輸入格式

第一行包含兩個整數 N 和 K。

以下 N 行每行包含兩個整數 Hi 和 Wi。

輸入保證每位小朋友至少能獲得一塊 1×1 的巧克力。

輸出格式

輸出切出的正方形巧克力最大可能的邊長。

數據範圍

1≤N,K≤105,
1≤Hi,Wi≤105

輸入樣例:

2 10
6 5
5 6

輸出樣例:

2

分析

二分法
在這裏插入圖片描述

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int N = 100010;
int h[N],w[N];
int n,k;  //n  巧克力塊數   k  小朋友數量

bool check(int mid)
{
    int res = 0;
    for(int i = 0;i < n;i++)
    {
        res += (h[i] / mid) * (w[i] / mid);
        if(res >= k) return true;
    }
    return false;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 0;i < n;i++)
        scanf("%d%d",&h[i],&w[i]);
    int l = 1,r = 1e5;
    while(l  < r)
    {
        int mid  = (l + r + 1) >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    printf("%d\n",r);
    return 0;
}

例題8 激光炸彈

題目

地圖上有 N 個目標,用整數Xi,Yi表示目標在地圖上的位置,每個目標都有一個價值Wi。

注意:不同目標可能在同一位置。

現在有一種新型的激光炸彈,可以摧毀一個包含 R×R 個位置的正方形內的所有目標。

激光炸彈的投放是通過衛星定位的,但其有一個缺點,就是其爆炸範圍,即那個正方形的邊必須和x,y軸平行。

求一顆炸彈最多能炸掉地圖上總價值爲多少的目標。

輸入格式

第一行輸入正整數 N 和 R ,分別代表地圖上的目標數目和正方形的邊長,數據用空格隔開。

接下來N行,每行輸入一組數據,每組數據包括三個整數Xi,Yi,Wi,分別代表目標的x座標,y座標和價值,數據用空格隔開。

輸出格式

輸出一個正整數,代表一顆炸彈最多能炸掉地圖上目標的總價值數目。

數據範圍

0≤R≤109
0<N≤10000,
0≤Xi,Yi≤5000
0≤Wi≤1000

輸入樣例:

2 1
0 0 1
1 1 1

輸出樣例:

1

分析

注意:
所有前綴和的問題,全部迴歸到 “1” 開始的局面,橫縱座標

初始化前綴和的操作:(二維的)
for(int i = 1;i <= n;i++)
    {
        for(int j = 1;j <= m;j++)
        {
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }
    }
求前綴和的操作
res = s[i][j] - s[i][j - R] - s[i - R][j] + s[i - R][j - R]
代碼
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

const int N  = 5010;

int n,m;  //n最大的x,m  最大的y
int s[N][N];  

int main()
{
    int cnt,R;  //cnt  數目  R  邊長
    cin >> cnt >> R;
    R = min(R,5001);
    n = m = R;
    while(cnt--)
    {
        int x,y,w;
        cin >> x >> y >> w;
        x++,y++;
        s[x][y] = w;
        n = max(n,x),m = max(m,y);
    }
    
    //初始化前綴和
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            s[i][j] +=s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; 
    
    int res = 0;
    for(int i = R;i <= n;i++)
        for(int j = R;j <= m;j++)
            res = max(res,s[i][j] - s[i][j - R] - s[i - R][j] + s[i - R][j - R]);
    cout << res << endl;
    return 0;
}

例題9 K倍區間

題目

給定一個長度爲 N 的數列,A1,A2,…AN,如果其中一段連續的子序列 Ai,Ai+1,…Aj 之和是 K 的倍數,我們就稱這個區間 [i,j] 是 K 倍區間。

你能求出數列中總共有多少個 K 倍區間嗎?

輸入格式

第一行包含兩個整數 N 和 K。

以下 N 行每行包含一個整數 Ai。

輸出格式

輸出一個整數,代表 K 倍區間的數目。

數據範圍

1≤N,K≤100000,
1≤Ai≤100000

輸入樣例:

5 2
1
2
3
4
5

輸出樣例:

6

分析

用前綴和

初始想法
在這裏插入圖片描述
優化1.0
在這裏插入圖片描述
再次優化
在這裏插入圖片描述
最終優化
在這裏插入圖片描述

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;

typedef long long LL;

const int N = 100010;
int n,k;
LL s[N],cnt[N];

int main()
{
    cin >> n >> k;
    for(int i = 1;i <=n;i++)
    {
        cin >> s[i];
        s[i] += s[i - 1];
    }
    LL res = 0;
    for(int i = 0; i <=n;i++)
    {
        res += cnt[s[i] % k];
        cnt[s[i] % k]++;
    }
    cout << res << endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章