• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
yingtaoqqq
博客园    首页    新随笔    联系   管理    订阅  订阅
二分

时间复杂度

连续自然数和

对一个给定的正整数 $M$,求出所有的连续的正整数段(每一段至少有两个数),这些连续的自然数段中的全部数之和为 $M$。

例子:$1998+1999+2000+2001+2002 = 10000$,所以从 $1998$ 到 $2002$ 的一个自然数段为 $M=10000$ 的一个解。

题目看到是连续数字相加,就可以用前缀和,可以化解为找两点L和R, 使得sum[R] - sum[L - 1] 的和为 m。这里就可以从L=1到n开始便利,lower_bound函数找到满足sum[L - 1] + m = sum[R]条件的下标,输出即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
    int n, i, ix;
    cin >> n;
    vector<int> sum(n + 5);
    sum[0] = 0;
    for (i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + i;
    for (i = 0; i < n; i++)
    {
        ix = lower_bound(sum.begin() + i + 1, sum.end(), n + sum[i]) - sum.begin();
        if (sum[ix] == sum[i] + n && ix - (i + 1) + 1 > 1)
        {
            cout << i + 1 << " " << ix << endl;
        }
    }
}

数列分段 Section II

对于给定的一个长度为 $N$ 的正整数数列 $A_{1\sim N}$,现要将其分成 $M$($M\leq N$)段,并要求每段连续,且每段和的最大值最小。

关于最大值最小:

例如一数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段。

将其如下分段:

$$[4\ 2][4\ 5][1]$$

第一段和为 $6$,第 $2$ 段和为 $9$,第 $3$ 段和为 $1$,和最大值为 $9$。

将其如下分段:

$$[4][2\ 4][5\ 1]$$

第一段和为 $4$,第 $2$ 段和为 $6$,第 $3$ 段和为 $6$,和最大值为 $6$。

并且无论如何分段,最大值不会小于 $6$。

所以可以得到要将数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段,每段和的最大值最小为 $6$。



解:
对于求最大值的最小值的问题是典型的二分问题,无法找出直接解决问题的思路,使用二分,以O(log2n)的速度找出答案 (在[0, 1e5]的范围内,精确到1e-8只需要44次)

这道题二分最大和的最小值,最大和设为x, 范围在[max of array, sum of array]中,规定了划分为m个区间,对于 x 较小的,会使得划分的区间数比m大, x较大的划分的区间数会比m小, 通过合理二分我们能找到合法且最小的区间。

(为了寻找x的最小合法值,二分向答案靠近,设check()函数的条件为 num <= k 即当前区间数较小或相等时,在二分时x选的过大或是在满足条件的一个值[不一定为最大值的最小值]将右边界缩小,而选取x过小时,left = mid + 1 说明最后二分结果一定在left = mid + 1 = right 这个情况是在x选取过小到x刚好合法的时候,即最后答案要求的最小满足的答案。由此我们也可以推出当改变条件边界,也可求出题目要求的最大值,改变check(num >= m), left = mid, right = mid - 1)

二分想好了,其实划分区间也很简单,就是通过贪心,尽量将能放在一起的放在一起。
在遍历整个数组的过程中,累加当前区间的总和sum,若此时加入a[i]后,sum + a[i] > x,这说明区间已满,需要再新开一个区间,每次新开记录数量,即得当前x的区间数num。

二分条件:

  1. 当分段数num > m时,指定最大值太小, left = mid + 1
  2. 当分段数num = m时,指定分段数合法,继续向左边看有无更小的,保留当前边界
  3. 当分段数num < m时, 指定最大值太大, right = mid;
int check(int x)
{
    int sum = 0, num = 1;
    for (int i = 1; i <= n; i++)
    {
        if (sum + a[i] <= x)
            sum += a[i];
        else
        {
            sum = a[i];
            num++;
        }
    }
    return num <= m;
}

while (left < right)
    {
        mid = (left + right) >> 1;
        if (check(mid))
        {
            right = mid;
        }
        else
            left = mid + 1;
    }

完整代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N], n, k;
bool check(int x)
{
    int duan = 0, now = 0;
    for (int i = 1; i <= n; i++)
    {
        if (now + a[i] <= x)
        {
            now += a[i];
        }
        else
        {
            now = a[i];
            duan++;
            if (duan > k)
                return 0;
        }
    }
    if (now)
        duan++;
    return duan <= k;
}
int main()
{
    int left, right, mid, maxx = 0, sum = 0;
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        maxx = max(a[i], maxx);
        sum += a[i];
    }
    left = maxx;
    right = sum;
    while (left < right)
    {
        mid = (left + right) >> 1;
        if (check(mid))
        {
            right = mid;
        }
        else
            left = mid + 1;
    }
    cout << right << endl;
}

你可能会问 如果x选的值是使得num合法的判断,但是没有一个区间达到了最大值,但其实此时达到合法值了,但是为了找到更小的会向左寻找更小的x,直到此时x满足某个区间为最大值。

抽象出最大值最小化,最小值最大化的模型:

找出二分范围, 用check条件判断,提前找出题目限制条件。




通往奥格瑞玛的道路

题目背景

在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。

有一天他醒来后发现自己居然到了联盟的主城暴风城。

在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。

题目描述

在艾泽拉斯,有 $n$ 个城市。编号为 $1,2,3,\ldots,n$。

城市之间有 $m$ 条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。

每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。

假设 $1$ 为暴风城,$n$ 为奥格瑞玛,而他的血量最多为 $b$,出发时他的血量是满的。如果他的血量降低至负数,则他就无法到达奥格瑞玛。

歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。

输入格式

第一行 $3$ 个正整数,$n,m,b$。分别表示有 $n$ 个城市,$m$ 条公路,歪嘴哦的血量为 $b$。

接下来有 $n$ 行,每行 $1$ 个正整数,$f_i$。表示经过城市 $i$,需要交费 $f_i$ 元。

再接下来有 $m$ 行,每行 $3$ 个正整数,$a_i,b_i,c_i$($1\leq a_i,b_i\leq n$)。表示城市 $a_i$ 和城市 $b_i$ 之间有一条公路,如果从城市 $a_i$ 到城市 $b_i$,或者从城市 $b_i$ 到城市 $a_i$,会损失 $c_i$ 的血量。

输出格式

仅一个整数,表示歪嘴哦交费最多的一次的最小值。

如果他无法到达奥格瑞玛,输出 AFK。

//大TLE TLE TLE
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4 + 5;
pair<int, int> f[N];
int a[N][N], dis[N], vis[N];
int n, m, b;
bool check(int x)
{
    // cout << "!11" << endl;
    int i, j, xx;
    vector<int> aa;
    for (i = 1; i <= x; i++)
        aa.push_back(f[i].first);
    for (i = 1; i <= n; i++)
        dis[i] = 1e18;
    memset(vis, 0, sizeof(vis));
    dis[1] = 0;
    int ff, minn;
    while (1)
    {
        minn = 0x3f;
        ff = -1;
        for (auto pos = aa.begin(); pos != aa.end(); ++pos)
        {
            xx = *pos;
            if (!vis[xx] && minn > dis[xx])
            {
                ff = xx;
                minn = dis[xx];
            }
        }
        if (ff == -1)
            break;
        vis[ff] = 1;
        for (auto pos = aa.begin(); pos != aa.end(); ++pos)
        {
            i = *pos;
            if (a[i][ff] != 0)
                dis[i] = min(dis[i], dis[ff] + a[i][ff]);
        }
    }
    cout << dis[n] << endl;
    if (dis[n] <= b)
        return true;
    return false;
}
bool cmp(pair<int, int> a, pair<int, int> b)
{
    if (a.second == b.second)
        return a.first < b.first;
    return a.second < b.second;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int i, x, y, ss, j;
    cin >> n >> m >> b;
    for (i = 1; i <= n; i++)
    {
        cin >> f[i].second;
        f[i].first = i;
    }
    for (i = 1; i <= n; i++)
    {
        for (j = 1; j <= i; j++)
        {
            a[i][j] = 1e9;
            a[j][i] = 1e9;
        }
    }
    int f1 = max(f[1].second, f[n].second);
    for (i = 0; i < m; i++)
    {
        cin >> x >> y >> ss;
        a[x][y] = ss;
        a[y][x] = ss;
    }
    sort(f + 1, f + 1 + n, cmp);
    int left = 1, right = n, mid;
    while (left < right)
    {
        mid = (left + right) >> 1;
        if (check(mid))
        {
            right = mid;
        }
        else
        {
            left = mid + 1;
        }
    }
    if (!check(right))
        cout << "AFK" << endl;
    else
    {
        cout << f[right].second << endl;
    }
}


解:
将最大值收入的最小值进行二分,check()函数内使用dijkstra算最短路径,注意最短路径时,要考虑路径的存储,用链式前向星来存,矩阵数组空间冗余过多会报错,在while循环内找当前最短dis时,也需要用到更优化的结构,把从1到每一个点的最短路径dis[n]用小根堆 + pair的形式存,即优先队列 priority_queue, vector>, greater>> que 使得在存储的时候拿取的时间在O(logn),这题也卡时间。 这样存储在 366ms 内存在3.36MB 原先粗糙代码在 477ms 128MB在大数据的情况下内存完全超限制

compare: 链式前向星的存储方式完全适配于dijkstra的算法,在寻找到最小的dis[n]时,重新更新所有未进队的节点dis值,直接便利head即可,存取都十分方便,还有就是时间复杂度上面,没有必要每一次check()都进行一次预处理把可以走的节点拉出来,直接在dijkstra更新的时候直接加上判断条件,收费值大于x的直接不更新,也不会进队,直接屏蔽。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4 + 5;
const int M = 1e5 + 5;
int n, m, b, f[N], vis[N], dis[N];
int cnt = 0, head[N];
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> que;
struct edge
{
    int to, next, s;

} ed[M];
void add(int xx, int yy, int ss)
{
    ed[++cnt].to = yy;
    ed[cnt].next = head[xx];
    head[xx] = cnt;
    ed[cnt].s = ss;
}
bool check(int x)
{
    int ddis, index, i, yy, cost;
    memset(vis, 0, sizeof(vis));
    for (i = 1; i <= n; i++)
        dis[i] = 1e9;
    if (f[1] > x || f[n] > x)
        return 0;
    dis[1] = 0;
    que.push({0, 1});
    while (!que.empty())
    {
        index = que.top().second;
        ddis = que.top().first;
        que.pop();
        if (vis[index])
            continue;
        vis[index] = 1;
        for (i = head[index]; i != -1; i = ed[i].next)
        {
            yy = ed[i].to;
            cost = ed[i].s;
            if (f[yy] <= x && !vis[yy] && dis[yy] > ddis + cost)
            {
                dis[yy] = ddis + cost;
                que.push({dis[yy], yy});
            }
        }
    }
    if (dis[n] <= b)
        return 1;
    return 0;
}
signed main()
{
    int i, xx, yy, ss, maxx = 0, minn = 1e9 + 10;
    cin >> n >> m >> b;
    for (i = 0; i < n; i++)
    {
        cin >> f[i + 1];
        maxx = max(maxx, f[i + 1]);
        minn = min(minn, f[i + 1]);
    }
    memset(head, -1, sizeof(head));
    for (i = 0; i < m; i++)
    {
        cin >> xx >> yy >> ss;
        add(xx, yy, ss);
        add(yy, xx, ss);
    }
    int left = minn, right = maxx, mid;
    while (left < right)
    {
        mid = (left + right) >> 1;
        if (check(mid))
        {
            right = mid;
        }
        else
        {
            left = mid + 1;
        }
    }
    if (!check(maxx))
    {
        cout << "AFK" << endl;
    }
    else
    {
        cout << left << endl;
    }
}

后置知识

链式前向星+小根堆dijstra

小根堆定义
//队列内类型,容器,排列方式(仿函数)
priority_queue<pair<int, int>, vector<int, int>, greater<pair<int, int>>>;
链式前向星定义:
struct edge
{
    int to, next, len;
} e[M];
添加边:
void add(int x, int y, int z)
{
    e[++t].len = z;
    e[t].to = y;
    e[t].next = head[x];
    head[x] = t;
}

便利方式:
    s2 = head[ff];
    while (s2 != -1)
    {
    }

进击的奶牛

题目描述

Farmer John 建造了一个有 $N$($2 \leq N \leq 10 ^ 5$) 个隔间的牛棚,这些隔间分布在一条直线上,坐标是 $x _ 1, x _ 2, \cdots, x _ N$($0 \leq x _ i \leq 10 ^ 9$)。

他的 $C$($2 \leq C \leq N$)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最小最近距离是多少呢?

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n, c, pos[N];
bool check(int x)
{
    int now = 0, num = 1;
    for (int i = 1; i < n; i++)
    {
        if (now < x)
        {
            now += pos[i + 1] - pos[i];
        }
        else
        {
            num++;
            now = pos[i + 1] - pos[i];
        }
    }
    if (now >= x)
        num++;
    return num >= c;
}
signed main()
{
    int i;
    cin >> n >> c;
    for (i = 1; i <= n; i++)
        cin >> pos[i];
    sort(pos + 1, pos + n + 1);
    int minn = 1e18, maxx = pos[n] - pos[1];
    for (i = 1; i < n; i++)
    {
        minn = min(minn, pos[i + 1] - pos[i]);
    }
    int left = minn, right = maxx, mid;
    while (left < right)
    {
        mid = (left + right + 1) / 2;
        if (check(mid))
            left = mid;
        else
            right = mid - 1;
    }
    cout << left << endl;
}

解:二分求相邻奶牛最小距离的最大值,板刷题目。二分距离范围在[最小的间距,第一个牛棚到最后一个的距离]。

posted on 2024-05-22 19:50  yingtaoqqq  阅读(34)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3