1. 程式人生 > 其它 >Acwing 第六章 貪心

Acwing 第六章 貪心

一、區間問題

905. 區間選點

思想:

將每個區間按右端點按小到大排序
從前往後依次列舉每個區間,如果當前區間已經包含選擇的點,則直接跳過,否則選擇當前區間的右端點

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

using namespace std;

const int N = 1e5+10;

struct Range
{
    int l;
    int r;
}range[N];
int n;
int ans = 1;//預設選一個點表示第一個區間
bool cmp(Range a,Range b)
{
    return a.r < b.r;
}

int main()
{
    cin>>n;
    for(int i = 0;i < n;i++)
    {
        cin>>range[i].l>>range[i].r;
    }
    sort(range,range + n,cmp); //將區間按右端點從小到大排序
    int last = range[0].r;
    for(int i = 1;i < n;i++)
    {
        if(range[i].l > last) //由於右端點從小到大排序,若當前區間左端點大於上一區間右端點
        //說明二者無交集,不可能用上一區間的點表示本區間,必須在本區間再找一個點
        {
            ans++;
            last = range[i].r;
        }
    }
    cout<<ans<<endl;
    return 0;
}

908. 最大不相交區間數量
左端點排序做法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
struct Range
{
  int l;
  int r;
}range[N];
int n,a,b;
int ans = 1;
bool cmp(Range a,Range b)
{
    return a.l < b.l;
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a>>b;
        range[i] = {a,b};
    }
    sort(range,range+n,cmp);
    int last = range[n-1].l;
    for(int i = n-2;i>=0;i--)
    {
        if(range[i].r < last)
        {
            ans++;
            last = range[i].l;
        }
    }
    cout<<ans<<endl;
    return 0;
}

906. 區間分組
給定 N 個閉區間 [ai,bi],請你將這些區間分成若干組,使得每組內部的區間兩兩之間(包括端點)沒有交集,並使得組數儘可能小。
輸出最小組數。
輸入格式
第一行包含整數 N,表示區間數。
接下來 N 行,每行包含兩個整數 ai,bi,表示一個區間的兩個端點。
輸出格式
輸出一個整數,表示最小組數。
資料範圍
1≤N≤105,
−109≤ai≤bi≤109

O(nlogn)

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

const int N = 1e5+10;
struct Range
{
    int l;
    int r;
}range[N];
int n,a,b;
bool cmp(Range a,Range b)
{
    return a.l < b.l;
}

int main()
{
    cin>>n;
    for(int i = 0;i < n;i++)
    {
        cin>>a>>b;
        range[i] = {a,b};
    }
    sort(range,range + n,cmp);//按左端點從小到大對區間排序
    priority_queue<int,vector<int>,greater<int>>heap; //定義小根堆
    for(int i = 0;i < n;i++)
    {
        if(heap.empty() || range[i].l <= heap.top()) //組數為0 或者 當前區間左端點比最靠左的組右端點小
        //說明不能放進組裡,否則會衝突,因此必須新開一個組
        {
            heap.push(range[i].r);
        }
        else //不會發生衝突,可以放進最靠左的組,更新組的最大右端點
        {
            heap.pop();
            heap.push(range[i].r);
        }
    }
    cout<<heap.size()<<endl;//堆中元素個數就是組的數量
    return 0;
}

112

907. 區間覆蓋

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

using namespace std;

const int N = 1e5+10;
struct Range
{
    int l;
    int r;
}range[N];

int a,b,n;
int st,ed;//線段的左右端點

bool cmp(Range a,Range b)
{
    return a.l < b.l;
}
int main()
{
    cin>>st>>ed;
    cin>>n;
    for(int i = 0;i < n;i++)
    {
        cin>>a>>b;
        range[i] = {a,b};
    }
    sort(range,range+n,cmp); //將區間按左邊界從小到大排序
    int res = 0,r = -2e9;//選擇的區間個數,當前選擇區間的右邊界
    bool state = false;//當前選擇的所有區間是否已全部包含線段
    for(int i = 0;i < n;i++)
    {
        int j = i;
        r = -2e9;
        for(j = i;j < n && range[j].l <= st;j++) //找出左端點不大於當前線段且右端點最大的區間
        {
            r = max(r,range[j].r);
        }
        if(r < st) //選定區間右端點比線段的左區間還小,說明根本無法覆蓋,直接跳出
        {
            res = -1;
            break;
        }
        res++; //判斷有覆蓋的可能性後加入當前區間到選擇中,選擇區間總數加1
        if(r >= ed) //若當前選定區間右端點大於線段右端點,說明成功覆蓋
        {
            state = true;
            break;
        }
        st = r;//將start更新成選定區間的右端點
        i = j - 1;//for迴圈i也要加,故賦值j-1,主要用於優化演算法
    }
    if(state == false) res = -1;
    cout<<res<<endl;
    return 0;
}

二、哈夫曼樹

資料結構課程中學過了,在此不多贅述
148. 合併果子

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

int n,sum,x;
priority_queue<int,vector<int>,greater<int>>heap;

int main()
{
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        cin>>x;
        heap.push(x);
    }
    while(heap.size() > 1)
    {
        int a = heap.top();
        heap.pop();
        int b = heap.top();
        heap.pop();
        sum += a + b;
        heap.push(a + b);
    }
    cout<<sum<<endl;
    return 0;
}

三、排序不等式

913. 排隊打水
總時間 = t1 * (n-1) + t2 * (n-2) + …… + tn-1 * 1 + tn * 0
思路
按照從小到大的順序,總時間最小

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

using namespace std;

const int N = 1e5 + 10;
int a[N],n;
long long sum;
int main()
{
    cin>>n;
    for(int i = 1;i <= n;i++) cin>>a[i];
    sort(a+1,a+n+1);
    for(int i = 1;i <= n;i++)
    {
        sum += a[i] * (n-i);
    }
    cout<<sum<<endl;
    return 0;
}

104. 貨倉選址

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

using namespace std;

const int N = 1e5 + 10;
int a[N],n,sum;

int main()
{
    cin>>n;
    for(int i = 0;i<n;i++) cin>>a[i];
    sort(a,a+n);
    for(int i = 0;i < n;i++)
    {
        sum+= abs(a[i] - a[n/2]); //倉庫取在中位數上,使得倉庫在所有a[1]~a[n],a[2]~a[n-1]距離最小
    }
    cout<<sum<<endl;
    return 0;
}

四、推公式

125. 耍雜技的牛

牛的重量和強壯度之和越大,越要放到下面

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

using namespace std;

const int N = 50005;
struct cow
{
    int w;
    int s;
    bool operator<(const cow& t)
    {
        return w+s < t.w + t.s;
    }
}cows[N];

int sum[N];
int n,w,s,danger = -0x3f3f3f3f;
int main()
{
    cin>>n;
    for(int i = 1;i <= n;i++)
    {
        cin>>w>>s;
        cows[i] = {w,s};
    }
    sort(cows+1,cows+n+1);
    for(int i = 1;i <= n;i++)
    {
        sum[i] = sum[i-1] + cows[i].w;
    }
    for(int i = 1;i <= n;i++)
    {
        danger = max(danger,sum[i-1] - cows[i].s);
    }
    cout<<danger<<endl;
    return 0;
}

五、時空複雜度分析

一般ACM或者筆試題的時間限制是1秒或2秒。
在這種情況下,C++程式碼中的操作次數控制在 107∼108107∼108 為最佳。

下面給出在不同資料範圍下,程式碼的時間複雜度和演算法該如何選擇:

n≤30n≤30, 指數級別, dfs+剪枝,狀態壓縮dp
n≤100n≤100 => O(n3)O(n3),floyd,dp,高斯消元
n≤1000n≤1000 => O(n2)O(n2),O(n2logn)O(n2logn),dp,二分,樸素版Dijkstra、樸素版Prim、Bellman-Ford
n≤10000n≤10000 => O(n∗n√)O(n∗n),塊狀連結串列、分塊、莫隊
n≤100000n≤100000 => O(nlogn)O(nlogn) => 各種sort,線段樹、樹狀陣列、set/map、heap、拓撲排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整體二分、字尾陣列、樹鏈剖分、動態樹
n≤1000000n≤1000000 => O(n)O(n), 以及常數較小的 O(nlogn)O(nlogn) 演算法 => 單調佇列、 hash、雙指標掃描、並查集,kmp、AC自動機,常數比較小的 O(nlogn)O(nlogn) 的做法:sort、樹狀陣列、heap、dijkstra、spfa
n≤10000000n≤10000000 => O(n)O(n),雙指標掃描、kmp、AC自動機、線性篩素數
n≤109n≤109 => O(n√)O(n),判斷質數
n≤1018n≤1018 => O(logn)O(logn),最大公約數,快速冪,數位DP
n≤101000n≤101000 => O((logn)2)O((logn)2),高精度加減乘除
n≤10100000n≤10100000 => O(logk×loglogk),k表示位數O(logk×loglogk),k表示位數,高精度加減、FFT/NTT