Chapter2 分治法

Chapter2 分治法

对分治法思想的体会

分治法的核心思想有三步

  • 确定问题的最小规模
if (STATEMENT) return;
  • 划分子问题

  • 合并子问题 (有的时候不需要合并)

二分搜索的应用

在顺序数组中查找值

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

  • Code
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define mod 1000000007
const ll maxn = 2e6 + 7;

ll BinSearch(ll a[], ll left, ll right, ll x) {
    ll mid;
    while (left <= right) {
        mid = (left + right) / 2;
        if (a[mid] == x) {
            return mid;
        } else if (a[mid] > x) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -1;
}

ll a[maxn];

int main() {
    ll x, n;
    cin>>n>>x;
    for (long long i = 0; i < n; ++i) {
        cin>>a[i];
    }
    printf("%lld", BinSearch(a, 0, n-1, x));


    return 0;
}
STL中的二分搜索
  • upper_bound() 返回的是被查序列中第一个大于查找值得指针
  • lower_bound()返回的是被查序列中第一个大于等于查找值的指针
int t = lower_bound(a+l,a+r,m) - a;

解释:在升序排列的a数组内二分查找 [ l , r ) [l,r) [l,r)区间内的值为 m m m的元素。返回 m m m在数组中的下标。
特殊情况:

  • 如果m在区间中没有出现过,那么返回第一个比m大的数的下标。
  • 如果m比所有区间内的数都大,那么返回r。这个时候会越界,小心。
  • 如果区间内有多个相同的m,返回第一个m的下标。

假定一个值来确定是否可行—猜测确定最优解

问题:求解满足某个条件 C ( x ) C(x) C(x)的最小 x x x

对于任意满足 C ( x ) C(x) C(x) x x x,如果所有的 x ′ > = x x'>=x x>=x也满足 C ( x ′ ) C(x') C(x)的话 ⟹ \Longrightarrow 可以二分求 M i n ( x ) Min(x) Min(x)

  • 初始化
    • 区间左端点 → \to 不满足 C ( x ) C(x) C(x)的值 e.g.0
    • 区间右端点 → \to 满足 C ( x ) C(x) C(x)的值
  • 每次取中点 m i d = ( l b + r b ) / 2 mid = (lb + rb) / 2 mid=(lb+rb)/2
  • 判断 C ( m i d ) C(mid) C(mid)是否满足并缩小范围,直到 [ l b , r b ) [lb,rb) [lb,rb)足够小, u b ub ub就是最小值
  • 求最大的方法类似.

e . g . e.g. e.g.

https://nanti.jisuanke.com/t/T1157

  • Code
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define mod 1000000007
const double PI=acos(-1.0);
const ll maxn = 2e6 + 7;
double a[maxn];

double Vpai(double r) {
    return PI * 1.0 * r * r;
}

int main() {
    ll n, f;
    double ans;
    scanf("%lld%lld", &n, &f);
    f++;//加上自己
    for (long long i = 0; i < n; ++i) {
        scanf("%lf", &a[i]);
        a[i] = Vpai(a[i]);
    }
    sort(a,a+n);
    double left = 0, right = a[n-1];
    while (fabs(left - right) > 0.000001) {
        double mid = (left + right) / 2.0;
        ll cnt = 0;
        for (long long i = 0; i < n; ++i) {
            cnt += a[i] / mid;
        }
        if (cnt < f) right = mid;
        else left = mid;
    }
    printf("%.3lf",left);
    return 0;
}

*最大化最小值

*最大化平均值

归并排序

  • Code
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define mod 1000000007
const ll maxn = 2e6 + 7;
ll ans;
ll n;

void merge(ll a[], ll b[], ll left, ll mid, ll right) {
    ll tmp = left;
    ll L = left;
    ll R = mid;
    ll leftEnd = mid - 1;
    ll rightEnd = right;
    ll NumElements = rightEnd - L + 1;
//    cout << left << ' ' << mid << ' ' << right << endl;
    while (L <= leftEnd && R <= rightEnd) {
        if (a[L] <= a[R]) b[tmp++] = a[L++];
        else b[tmp++] = a[R++];
    }
    while (L <= leftEnd) b[tmp++] = a[L++];
    while (R <= rightEnd) b[tmp++] = a[R++];
    for (long long i = 0; i < NumElements; ++i, rightEnd--) {
        a[rightEnd] = b[rightEnd];
    }
}

void mergeSort(ll a[], ll b[], ll left, ll right) {
    if (left >= right) return;
    ll mid = (left + right) / 2;
    mergeSort(a, b, left, mid);
    mergeSort(a, b, mid + 1, right);
    merge(a, b, left, mid + 1, right);
}

ll b[maxn];
ll a[maxn];

int main() {

    scanf("%lld", &n);
    for (long long i = 0; i < n; ++i) {
        scanf("%lld", &a[i]);
    }
    ans = 0;
    mergeSort(a, b, 0, n - 1);
    for (long long i = 0; i < n; ++i) {
        if (i == n - 1) {
            cout << a[i];
            break;
        }
        cout << a[i] << ' ';
    }

    return 0;
}

快速排序

原理略,下就编程细节做一些分析

  • 关于partition函数
ll partition(ll a[], ll left, ll right) {
    ll x = left;
    ll i = left + 1;
    ll j = right;
    while (true) {
        while (i <= j && a[i] <= a[x]) i++;
        while (a[j] > a[x]) j--;
        if (i > j) break; //if(i>=j) break;????
        swap(a[i], a[j]);
    }
    swap(a[left], a[j]);
    return j;
}

有以下几个编程易错点

  1. Line 3i = left + 1容易错写为i = left这样会导致没有选取到旗标元素导致死循环
  2. Line 6
while (i <= j && a[i] <= a[x]) i++;

易错写为

while (i <= j && a[i] < a[x]) i++;

while (i < j && a[i] < a[x]) i++;

while (a[i] <= a[x]) i++;

都可能导致无法正确排序或者死循环

  1. Line 7
while (a[j] > a[x]) j--;

易错写为

while (a[j] >= a[x]) j--;
  1. Line 8
 if (i > j) break;

易错写为

 if (i >= j) break;
  1. Line 11
swap(a[left], a[j]);

易错写为

swap(a[i], a[j]);
  • qSort函数
void qSort(ll a[], ll left, ll right) {
    if (left >= right) return;
    ll mid = partition(a, left, right);
    qSort(a, left, mid - 1);
    qSort(a, mid + 1, right);
}
  • find函数
ll find(ll a[], ll left, ll right, ll k) {
    ll pos = partition(a, left, right);
    if (pos == k - 1) return a[k - 1];
    if (pos > k - 1) find(a, left, pos - 1, k);
    else find(a, pos + 1, right, k);
}
  • Code
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 6000000;

ll partition(ll a[], ll left, ll right) {
    ll x = left;
    ll i = left + 1;
    ll j = right;
    while (true) {
        while (i <= j && a[i] <= a[x]) i++;
        while (i <= j && a[j] > a[x]) j--;
        if (i > j) break; //
        swap(a[i], a[j]);
    }
    swap(a[left], a[j]);
    return j;
}

void qSort(ll a[], ll left, ll right) {
    if (left >= right) return;
    ll mid = partition(a, left, right);
    qSort(a, left, mid - 1);
    qSort(a, mid + 1, right);
}

ll find(ll a[], ll left, ll right, ll k) {
    ll pos = partition(a, left, right);
    if (pos == k - 1) return a[k - 1];
    if (pos > k - 1) find(a, left, pos - 1, k);
    else find(a, pos + 1, right, k);
}

ll a[maxn];

int main() {
    ll n, m, t;
    scanf("%lld", &t);
    while (t--) {
        scanf("%lld%lld", &n, &m);
        for (ll i = 0; i < n; i++) scanf("%lld", &a[i]);
        printf("%lld\n", find(a, 0, n - 1, m));
    }

    return 0;
}

PS:对n个数快速排序

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 6000000;

ll partition(ll a[], ll left, ll right) {
    ll x = left;
    ll i = left + 1;
    ll j = right;
    while (true) {
        while (i <= j && a[i] <= a[x]) i++;
        while (a[j] > a[x]) j--;
        if (i > j) break;
        swap(a[i], a[j]);
    }
    swap(a[left], a[j]);
    return j;
}

void qSort(ll a[], ll left, ll right) {
    if (left >= right) return;
    ll mid = partition(a, left, right);
    qSort(a, left, mid - 1);
    qSort(a, mid + 1, right);
}

ll a[maxn];
int main() {
    ll n;
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for (long long i = 0; i < n; ++i) {
        cin >> a[i];
    }
    qSort(a, 0, n - 1);
    for (long long i = 0; i < n; ++i) {
        if (i == n - 1) {
            cout << a[i];
            break;
        }
        cout << a[i] << " ";
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值