基礎演算法

跟一些有的沒的

講師介紹

226

在這邊講名字好像google的到所以不寫

TOI 一階選訓營結業

Handle: 跟DC一樣

自學資源

複雜度複習

int n;
cin >> n;
long long ans = 0;
for (long long i=1; i <= n; i++){
    ans += i;
}
cout << ans;
int n;
cin >> n;
long long ans = 0;
for (long long i=1; i <= n; i++){
    ans += i;
}
cout << ans;

\(O(n)\)

int n;
cin >> n;
long long ans = n*(n+1)/2;
cout << ans;
int n;
cin >> n;
long long ans = n*(n+1)/2;
cout << ans;

\(O(1)\)

long long n, count = 0, five = 5;
cin >> n;
while (five <= n){
    count += n/five;
    five *= 5;
}
cout << count;
long long n, count = 0, five = 5;
cin >> n;
while (five <= n){
    count += n/five;
    five *= 5;
}
cout << count;

\(O(log n)\)

 

競賽上的應用

電腦一秒鐘大約可以跑\(10^8\)~\(10^9\)次運算,所以可以大致估算運行時間。

範圍 對應的複雜度
n < 10 O(n!)
n < 20 O(2^n)
n < 500 O(n^3) 
n < 5000 O(n^2)
n < 1e5 O(nlogn)
n < 1e6 O(n)
n < 1e18 O(logn)

slides.com 不讓我在表格打Latex QQ

然後等一下就會知道為什麼有這麼多log了

演算法/資料結構

這兩個是什麼東西

演算法(Algorithm)就是一系列指揮電腦做事的步驟。它會得到一些資訊(輸入),執行一些步驟,最後會輸出結果。

 

資料結構(Data Structure) 則是在電腦中儲存東西的方式,像是上個禮拜講的所有東西。

 

在比賽中,考驗的就是你設計出好的演算法的能力,過程中會應用到許多資料結構。

排序

看得出來它在幹嘛嗎

看得出來它在幹嘛嗎

int n;
cin >> n;
vector<int> arr(n);
for(int i = 0; i < n; i++) cin >> arr[i];
for(int i = 0; i < n; i++){
    int mini = arr[i], min_pos = i;
    for(int j = i; j < n; j++){
        if(arr[j] < mini){
            mini = arr[j];
            min_pos = j;
        }
    }
    swap(arr[min_pos], arr[i]);
}

複雜度?

看得出來它在幹嘛嗎

int n;
cin >> n;
vector<int> arr(n);
for(int i = 0; i < n; i++) cin >> arr[i];
for(int i = 0; i < n; i++){
    int mini = arr[i], min_pos = i;
    for(int j = i; j < n; j++){
        if(arr[j] < mini){
            mini = arr[j];
            min_pos = j;
        }
    }
    swap(arr[min_pos], arr[i]);
}

複雜度?

\(O(n^2)\)

這樣好像太慢欸

Merge Sort

一樣想一下它在做什麼

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4 5

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4 5 6

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4 5 6 7

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4 5 6 7 7

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4 5 6 7 7 8

Merge sort

把兩個排序好的陣列合併?

1 4 5 7 8 10
2 3 6 7 9 12

1 2 3 4 5 6 7 7 8 9 10 12

Merge sort

排序好的陣列哪裡來?

Merge sort

排序好的陣列哪裡來?

Merge sort

仔細看圖可以發現複雜度變成了\(O(nlogn)\)

實作上會使用遞迴的技巧:先將左右兩區間遞迴處理,再將兩區間合併。

Merge sort

#include<iostream>
#include<vector>
using namespace std;
vector<int> vec;
void merge(int l, int r){
    if(l == r) return;
    int mid = (l + r) / 2;
    merge(l, mid); 
    merge(mid + 1, r); //分兩邊遞迴
    int i = l, j = mid + 1; // i為第一個陣列 j為第二個
    vector<int> tmp; 
    while(i <= mid && j <= r){
        if(vec[i] < vec[j]){
            tmp.push_back(vec[i]);
            i++;
        }
        else{
            tmp.push_back(vec[j]);
            j++;
        }
    }
    while(i <= mid) tmp.push_back(vec[i]), i++;
    while(j <= r) tmp.push_back(vec[j]), j++;
    for(int k = l; k <= r; k++){
        vec[k] = tmp[k-l];
    }
    return;
}
int main(){
    int n;
    cin >> n;
    vec.resize(n);
    for(int i = 0; i < n; i++){
        cin >> vec[i];
    }
    merge(0, n - 1);
    for(int i = 0; i < n; i++){
        cout << vec[i] << " ";
    }
}

內建函式

每次想排序都要寫一長串太麻煩了,可以用#include<algorithm>裡頭的sort

int N = 1e5;
int arr[N];
for(int i=0; i < N; i++) cin >> arr[i];
sort(arr, arr + N);
for(int i=0; i < N; i++) cout << arr[i];

內建函式

vector版

int N = 1e5;
vector<int> vec(N);
for(int i=0; i < N; i++) cin >> vec[i];
sort(vec.begin(), vec.end());
for(int i=0; i < N; i++) cout << vec[i];

內建函式

從大排到小

#include <bits/stdc++.h>
using namespace std;

const int SIZE = 1e5 + 5;

int n;
int a[SIZE];

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    sort (a + 1, a + n + 1, greater<int>());

    for (int i = 1; i <= n; i++) cout << a[i] << " \n";
}

內建函式

可以自己定義排序條件

在這裡是將數字照除以k的餘數排序

#include <bits/stdc++.h>
using namespace std;
 
const int SIZE = 1e5 + 5;
 
int n, k;
int a[SIZE];
 
// 自訂比較函式
bool cmp (int x, int y) {
    int mx = x % k, my = y % k;
    if (mx != my) return mx > my;
    return x < y;
}
 
int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort (a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; i++) cout << a[i] << " \n";
}

內建函式

也可以使用lambda

#include <bits/stdc++.h>
using namespace std;

const int SIZE = 1e5 + 5;

int n, k;
int a[SIZE];

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    
    sort (a + 1, a + n + 1, [] (int x, int y) {
        int mx = x % k, my = y % k;
        return mx != my ? mx > my : x < y;
    });
    
    for (int i = 1; i <= n; i++) cout << a[i] << " \n";
}

例題

給定一個有\(N\)個數字的序列\(A\),計算有幾對數對\((i,j)\)使得\(i<j\)且\(a_i>a_j\)

\(N<10^5\)

給定一個有\(N\)個數字的序列\(A\),計算有幾對數對\((i,j)\)使得\(i<j\)且\(a_i>a_j\)

\(N<10^5\)

還記得Merge Sort嗎?我們可以在合併兩個陣列的時候順便計算逆序數對的數量。複雜度\(O(nlogn)\)

Code待補

#include<iostream>
#include<vector>
#define int long long
using namespace std;
vector<int> vec;
int merge(int l, int r){
    if(l == r) return 0;
    int mid = (l + r) / 2, ans=0;
    ans += merge(l, mid); 
    ans += merge(mid + 1, r); //分兩邊遞迴
    int i = l, j = mid + 1; // i為第一個陣列 j為第二個
    vector<int> tmp; 
    while(i <= mid && j <= r){
        if(vec[i] <= vec[j]){
            tmp.push_back(vec[i]);
            i++;
        }
        else{
            tmp.push_back(vec[j]);
            ans += mid - i + 1;
            j++;
        }
    }
    while(i <= mid) tmp.push_back(vec[i]), i++;
    while(j <= r) tmp.push_back(vec[j]), j++;
    for(int k = l; k <= r; k++){
        vec[k] = tmp[k-l];
    }
    return ans;
}
signed main(){
    int n;
    int cnt = 1;
    while(cin >> n){
    	if(n == 0) break;
    	vector<int> tmp(0);
    	swap(vec,tmp);
    	vec.resize(n);
    	for(int i = 0; i < n; i++){
        	cin >> vec[i];
    	}
    	cout << "Case #"<< cnt++ << ": "<< merge(0, n - 1)  << "\n";
	}
}

題目

 

枚舉

枚舉

就是暴力的意思。

教這個幹嘛?

枚舉

就是暴力的意思。

教這個幹嘛?

類似第一子題的概念。

很多時候也可以用暴力來驗證解答或想法

遞迴枚舉

有時候你需要枚舉\(\binom{n}{k}\)種從n個東西中取k個的方法,可是沒辦法寫n層迴圈。

這時我們就又需要遞迴的幫助。在程式中我們會順便紀錄現在取到第幾個數字,當取完就中止遞迴。

遞迴枚舉

#include<iostream>
#include<vector>
using namespace std;
vector<int> cur;
void solve(int n, int k, int cur_n, int cur_k){
    if(n+1 == cur_n && k == cur_k){
        for(int i=0; i<k; i++){
            cout << cur[i] << " ";
        }
        cout << "\n";
        return;
    }
    if(n+1 == cur_n){
        //沒取滿k個,直接終止程式
        return;
    }
    if(cur_k < k){
        //取當前數字
        cur.push_back(cur_n);
        solve(n, k, cur_n + 1, cur_k + 1);
        cur.pop_back(); //很重要!記得拿出來
    }
    //不取當前數字
    solve(n, k, cur_n + 1, cur_k);
}
int main(){
    int n,k;
    cin >> n >> k;
    solve(n, k, 1, 0);
}

0/1 枚舉

有一些東西,你想要取其中一些算答案。

還是可以遞迴,只是比較難寫。

0/1 枚舉

有一些東西,你想要取其中一些算答案。

還是可以遞迴,只是比較難寫。

每個元素都是取(1)或不取(0),是不是可以寫成二進位?

0/1 枚舉

有一些東西,你想要取其中一些算答案。

還是可以遞迴,只是比較難寫。

每個元素都是取(1)或不取(0),是不是可以寫成二進位?

 

所以可以從\(1\)枚舉到\(2^n-1\),如果二進位的第\(i\)位是\(1\),則代表要取這個數字。

排列枚舉

顧名思義就是要枚舉所有排列

這裡可以用C++內建的next_permutation

 

 

接下來會用題目講解每種枚舉的寫法

有一個西洋棋棋盤,你想要在上面放\(8\)個皇后,使得皇后都不互相攻擊別人。棋盤上有些格子是髒的,不能放皇后。問有幾種可能的排法。

遞迴枚舉!可是\(\binom{64}{8}\)超大,會TLE

 

遞迴枚舉!可是\(\binom{64}{8}\)超大,會TLE

為了加快速度,可以ㄧ發現同一行有兩個皇后就中止遞迴。這種方法叫做剪枝。

有\(N\)顆蘋果,你想把它分成兩堆,使得兩邊重量差距最小。

\(N<20\)

就是剛剛的0/1枚舉

#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
int main(){
    int n;
    cin >> n;
    vector<ll> vec(n);
    for(int i = 0; i < n; i++) cin >> vec[i];
    ll ans = 1e18;
    for(int i = 0; i < (1 << n); i++){
        ll a = 0, b = 0;
        for(int j = 0; j < n; j++){
            if(i & (1 << j)){
                a += vec[j];
            }
            else{
                b += vec[j];
            }
        }
        ans = min(ans, llabs(a - b));
    }
    cout << ans;
}

給一個長度為\(N\)的字串,照字典序輸出所有重組完可能出現的字串

\(N<8\)

利用next_permutation (記得要先排序!)

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
int main(){
    string s;
    cin >> s;
    sort(s.begin(), s.end());
    vector<string> vec;
    int count=0;
    do{
        count++;
        vec.push_back(s);
    }while(next_permutation(s.begin(), s.end()));
    cout << count << "\n";
    for(auto i:vec){
        cout << i << "\n";
    }
}

聰明的枚舉

聰明的枚舉

有時候只需要枚舉一部分東西,就能計算答案了

 

例:給\(N\),請因式分解\(N\)

\(N\leq10^8\)

聰明的枚舉

有時候只需要枚舉一部分東西,就能計算答案了

 

例:給\(N\),請因式分解\(N\)

\(N\leq10^8\)

 

枚舉\(2\)~\(\sqrt{N}\)就行了!剩下來的那個數字一定是質數。

聰明的枚舉

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;

    bool f = 0; // 之前是否有輸出數字
    for (int i = 2, sq = sqrt (n); i <= sq; i++) { // sqrt (n) 在一開始先計算好,避免大量的 sqrt 運算
        int cnt = 0;
        while (n % i == 0) {
            cnt++;
            n /= i;
        }
        if (cnt > 0) {
            if (f == 0) f = 1;
            else cout << " * ";
            if (cnt == 1) cout << i;
            else cout << i << '^' << cnt;
        }
    }

    if (n > 1) {
        if (f == 1) cout << " * ";
        cout << n;
    }
    cout << '\n';
}

題目

CSES 1081

提示:\(\sum_{i=1}^N \frac{N}{i} \simeq NlogN\)

二分搜

老到牙的遊戲

我現在心裡在想一個數字\(N\),你可以猜數字,我會跟你講比較大還是比較小。

老到牙的遊戲

我現在心裡在想一個數字\(N\),你可以猜數字,我會跟你講比較大還是比較小。

 

大家的直覺應該都會切一半吧。這就是所謂的二分搜,因為每次範圍都會變一半,所以只要猜\(O(logN)\)次就猜的中了。

老到牙的遊戲

我現在心裡在想一個數字\(N\),你可以猜數字,我會跟你講比較大還是比較小。

 

大家的直覺應該都會切一半吧。這就是所謂的二分搜,因為每次範圍都會變一半,所以只要猜\(O(logN)\)次就猜的中了。

 

有時候你只能驗證答案可不可行,不能直接算出來,這時候就可以仰賴二分搜。

一些範例

有一些數字,我想要找出裡面第一個比\(x\)大的。

 

一些範例

有一些數字,我想要找出裡面第一個比\(x\)大的。

把陣列排序好之後就可以二分搜了。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(){
    int n, x;
    cin >> n >> x;
    vector<int> vec(n);
    for(int i = 0; i < n; i++){
        cin >> vec[i];
    }
    sort(vec.begin(), vec.end());
    int l = 0, r = n - 1; //答案有可能在[l,r]
    while(l < r){
        int mid = (l + r) / 2;
        if(vec[mid] >= x){
            r = mid;
        }
        else{
            l = mid + 1;
        }
    }
    cout << l + 1;
}

debug

程式在空轉?可以思考一下等號成立時會發生什麼事,並用此推測要讓\(r=mid\)還是\(r=mid-1\),要讓\(l=mid\)還是\(l=mid+1\)

 

\(mid\)的選法可能也有影響,

跑不動可以換成\(\frac{l+r}{2}+1\)試試看

 

 

debug

如果\(l\)跟\(r\)有可能是負數,那有可能被c++的奇怪除法卡到。

這時可以把\(mid=\frac{l+r}{2}\)改為\(mid=l+\frac{r-l}{2}\)

 

最後要很小心overflow的問題。

內建函式

剛剛的程式也可以用內建函式取代,記得還是要sort

 

 

 

vector<int> vec = {5, 4, 9, 10, 2};
sort(vec.begin(), vec.end());
int lb = lower_bound(vec.begin(), vec.end(), 9) - vec.begin(); //第一個>=x的
int rb = upper_bound(vec.begin(), vec.end(), 9) - vec.begin(); //第一個>x的

set<int> s = {5, 4, 9, 10, 2};
set<int>::iterator lb = s.lower_bound(9); // set記得要用這個,不然複雜度會退化。然後set不能知道是第幾大,只能知道數字大小

浮點數上二分搜

可以設定一個很小的數字,如果r-l比它小就跳出

 

 

 

double eps = 1e-6;
double l = 0, r = 1e9;
while(r - l > eps){
    //do something
}

對答案二分搜

什麼時候需要自己寫二分搜呢?

對答案二分搜

什麼時候需要自己寫二分搜呢?

題目問你最大(小)的答案是多少時,如果只要比答案小(大)都符合題目條件,就可以使用二分搜。

 

二分搜的題目也有一個特點:很容易確認符不符合條件,但不能直接算出答案。

例題

有\(N\)臺機器,第\(i\)臺機器做一個產品要花\(k_i\)秒,機器可以同時運作。最少要花幾秒才能做完\(t\)個產品?

\(N<2*10^5\)

假如\(x\)秒做得完,那\(x+1\)秒也做得完,所以可以用二分搜。

假如\(x\)秒做得完,那\(x+1\)秒也做得完,所以可以用二分搜。

 

要怎麼知道\(x\)秒做不做得完?

#include<iostream>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
int main(){
    ll n, t;
    cin >> n >> t;
    vector<ll> vec(n);
    for(int i = 0; i < n; i++) cin >> vec[i];
    ll l = 1,r = 1e18;
    while(l<r){
        ll mid = (l + r) >> 1,sum = t;
        for(auto x:vec){
            sum -= mid / x;
            if(sum < 0) break;
        }
        if(sum <= 0) r = mid;
        else l = mid + 1;
    }
    cout << l;
}

題目

三分搜

三分搜

跟二分搜差不多,蠻少用到的。

是用來在二次函數那樣的函數找最小(大)值

三分搜

跟二分搜差不多,蠻少用到的。

是用來在二次函數那樣的函數找最小(大)值

題目

分治

分治

還記得Merge Sort嗎?

Merge Sort 大致可以分成三個部分:

 

Divide:把大問題切成小問題

Conquer:把小問題遞迴,解決

Merge:將兩個解決後的小問題合併

分治

這個思想其實可以用在非常多地方,常用到以後每堂課它都會不停地出現。

 

今天會講解一些例題,讓你了解分治的概念。

以後的其他課會學到更多用到分治的演算法。

例題

平面上有\(N\)個點,請找出最近的兩個點

\(N\leq2*10^5\)

沿用分治的思想,把平面切成兩半。

沿用分治的思想,把平面切成兩半。

那這樣答案會是\(min(left, right, middle)\)

沿用分治的思想,把平面切成兩半。

那這樣答案會是\(min(\)左,右,左右之間\()\)

左邊跟右邊可以遞迴下去算,啊橫跨中間的呢?

沿用分治的思想,把平面切成兩半。

那這樣答案會是\(min(\)左,右,左右之間\()\)

左邊跟右邊可以遞迴下去算,啊橫跨中間的呢?

會不會全部的點都在裡面,這樣複雜度還是\(O(n^2)\)?

可以照y座標排序,檢查相鄰的八個點就好!

複雜度\(O(nlog^2n)\)

複雜度怎麼算?

分治的複雜度很玄學,可以用主定理(Master Theorem)計算,但是講師不會,有興趣的可以自己看看。(或是如果有剩時間可以一起研究)

複雜度怎麼算?

分治的複雜度很玄學,可以用主定理(Master Theorem)計算,但是講師不會,有興趣的可以自己看看。(或是如果有剩時間可以一起研究)

 

比較簡單的方法是把遞迴的圖畫出來,像之前Merge Sort一樣,再來看做了幾次運算。

給定\(a,b\),請計算\(a^b\) mod \(10^9+7\)

\(a,b\leq10^9\)

直接用迴圈算?

 

\(O(b)\), TLE

假設b是偶數

一樣沿用分治的思想,把\(a^b\)變成\((a^{b/2})^{2}\)。

那就先把\(a^{b/2}\)算出來,再自己乘自己就好了。

 

b是奇數?

假設b是偶數

一樣沿用分治的思想,把\(a^b\)變成\((a^{b/2})^{2}\)。

那就先把\(a^{b/2}\)算出來,再自己乘自己就好了。

 

b是奇數?

變成\(a^{b/2}*a^{b/2}*a\)

假設b是偶數

一樣沿用分治的思想,把\(a^b\)變成\((a^{b/2})^{2}\)。

那就先把\(a^{b/2}\)算出來,再自己乘自己就好了。

 

b是奇數?

變成\(a^{b/2}*a^{b/2}*a\)

 

複雜度?

假設b是偶數

一樣沿用分治的思想,把\(a^b\)變成\((a^{b/2})^{2}\)。

那就先把\(a^{b/2}\)算出來,再自己乘自己就好了。

 

b是奇數?

變成\(a^{b/2}*a^{b/2}*a\)

 

複雜度?

\(O(logb)\)

#include<iostream>
#define int long long
using namespace std;
const int modulo = 1e9+7;
int power(int a, int b){
	if(b == 0) return 1;
	else if(b % 2 == 1) return (a * power(a, b - 1)) % modulo;
	else{
		int tmp=power(a, b / 2);
		return tmp * tmp % modulo;
	}
}
signed main(){
	int n;
	cin >> n;
	int a, b;
	for(int i = 0; i<  n; i++){
		cin >> a >> b;
		cout << power(a, b) << "\n";
	}
	return 0;
}

題目

雙指標

有一個長度為\(N\)的陣列,請找出兩個數字使得它們加起來是\(x\)

\(N\leq2*10^5\)

\(x\leq10^9\)

不會?把陣列排序看看

2 3 5 6 9 10

\(x=14\)

再放兩個指標

2 3 5 6 9 10

\(x=14\)

再放兩個指標

2 3 5 6 9 10

\(x=14\)

\(2+10<14\)

再放兩個指標

2 3 5 6 9 10

\(x=14\)

\(3+10<14\)

再放兩個指標

2 3 5 6 9 10

\(x=14\)

\(5+10>14\)

再放兩個指標

2 3 5 6 9 10

\(x=14\)

\(5+9=14\)

雙指標

這就是雙指標最基本的應用。

雙指標的特性:陣列遵守某種單調性時(在這裡是由小到大),可以用兩個指標去搜索。因為指標只會從左走到右(或右到左)一次,所以複雜度是\(O(n)\)

 

還記得Merge Sort嗎?合併的過程就是雙指標的一種。

題目

CEOI '10 D2 pC

會用到DP的概念,不知道可以跳過

前綴和

前綴和

就是把前綴全部加起來。

1 5 2 3 1 2

前綴和

就是把前綴全部加起來。

1 5 2 3 1 2
1 6 8 11 12 14

前綴和

這可以拿來幹嘛?

1 5 2 3 1 2
1 6 8 11 12 14

前綴和

這可以拿來幹嘛?

 

1 5 2 3 1 2
1 6 8 11 12 14

我想要知道\(a_2+a_3+a_4+a_5\)

本來要一個一個加起來,但有了前綴和?

前綴和

這可以拿來幹嘛?

 

1 5 2 3 1 2
1 6 8 11 12 14

我想要知道\(a_2+a_3+a_4+a_5\)

本來要一個一個加起來,但有了前綴和?

\(\sum_{i=c}^d{a_i} = pref_d - pref_{c-1}\)

例題

給一個長度為\(N\)的陣列,接著有\(Q\)筆詢問,問一段子陣列[a,b]的和

\(N,Q\leq2*10^5\)

建造前綴和陣列,就可以用一次減法回答一個詢問

#include<iostream>
#include<vector>
#define ll long long
using namespace std;
int main(){
    int n, q;
    cin >> n >> q;
    vector<int> vec(n+1);
    for(int i = 1; i <= n; i++) cin >> vec[i];
    vector<int> pre(n+1);
    for(int i = 1; i <= n; i++) pre[i] = pre[i-1] + vec[i];
    int a, b;
    for(int i = 0; i < q; i++){
    	cin >> a >> b;
        cout << pre[b] - pre[a-1] << "\n";
    }
}

給一個長度為\(N\)的陣列,請求出最大區間和

\(N\leq2*10^5\)

蓋了前綴和陣列後,問題變成:

讓\(pref_r\)盡量最大,\(pref_l\)盡量最小

蓋了前綴和陣列後,問題變成:

讓\(pref_r\)盡量最大,\(pref_l\)盡量最小

 

那就用一個變數\(mini\)記錄目前以來最小的值!

#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
signed main(){
    int n;
    cin >> n;
    vector<int> vec(n+1);
    for(int i = 1; i <= n; i++) cin >> vec[i];
    vector<int> pre(n+1);
    for(int i = 1; i <= n; i++) pre[i] = vec[i] + pre[i-1];
    int maxi = -1e9-7, mini = 1e9+7; //maxi = 目前看到的最大區間和,mini = 目前看到最小的前綴和
    for(int i = 0; i < n; i++){
        mini = min(mini, pre[i]);
        maxi = max(maxi, pre[i+1] - mini);
    }
    cout << maxi;
}

題目

CSES 1661

提示:map

Made with Slides.com