二分

二分

1、单调性
2、趋势:二分条件:check(mid)>k,能够得到恰好的等于 k 的值
3、check需要满足的条件

CF1251D Salary Changing

链接:https://codeforces.com/contest/1251/problem/D

题意:给定 n 个区间表示 n 个员工的预期工资区间,总共有 s 元钱(保证能发最低工资),问怎样分发工资才能够让工资的中位数最大,求这个最大的中位数。(s 不必用完)

思路:思考每一个区间发多少钱可定不现实,问题求的是中位数,只需着眼中位数即可。

  • 二分答案。可以发现中位数越大,所发工资越多。
  • 最大值表现为趋向,中位数表现为 ( n + 1 ) / 2 (n+1)/2 (n+1)/2 ,设为 k 。
  • check 的时候,按照中位数的条件,首先有 恰好 k 个工资是大于等于 mid 。且总工资小于等于 s
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=2e5+10;
int t,n;
ll s,sum;
pair<int,int> opt[maxn];
bool check(int mid)
{
    int k=0;
    ll total=sum;
    for(int i=n; i>=1; --i)
    {
        if(opt[i].se>=mid)
        {
            k++;
            if(mid>=opt[i].fi) total+=mid-opt[i].fi;
            if(k==(n+1)/2) break;
        }
    }
    return k==(n+1)/2&&total<=s;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        sum=0;
        scanf("%d%lld",&n,&s);
        int L=1e9,R=0;
        for(int i=1; i<=n; ++i)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            opt[i]= {l,r};
            L=min(L,l);
            R=max(R,r);
            sum+=l;
        }
        sort(opt+1,opt+1+n);
        while(L<R)
        {
            int mid=(L+R+1)>>1;
            if(check(mid)) L=mid;
            else R=mid-1;
        }
        printf("%d\n",L);
    }
    return 0;
}

WOJ1033组装电脑

链接:http://oj.51xiaocheng.com/problem/WOJ1033

题意:给定 n 个配件 m 块钱,每个配件有类型、名字、价格、质量,四个属性。将这 n 个配件中,每类配件都至少买一个。问最大的最差质量是多少?

思路

  • 二分。最差质量越大,价格越高。
  • 最大是趋向,最差是check的条件。
  • check时,需要选择价格最便宜的且质量大于等于 mid 的配件,不存在则mid不合理。
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=2e5+10;
int t,n,total;
map<string,vector<pair<int,int> > > mp;
bool check(int mid)
{
    ll sum=0;
    for(auto x: mp)
    {
        string type=x.fi;
        int price=1e7;
        for(auto y: mp[type])
            if(y.se>=mid) price=min(price,y.fi);
        if(price==1e7) return 0;
        sum+=price;
    }
    return sum<=total;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&total);
        mp.clear();
        string type,name;
        int price,quality;
        int L=1e9,R=0;
        for(int i=1; i<=n; ++i)
        {
            cin>>type>>name>>price>>quality;
            mp[type].push_back({price,quality});
            L=min(L,quality);
            R=max(R,quality);
        }
        while(L<R)
        {
            int mid=(L+R+1)>>1;
            if(check(mid)) L=mid;
            else R=mid-1;
        }
        printf("%d\n",L);
    }
    return 0;
}

Copying Books POJ-1505

链接:http://poj.org/problem?id=1505

题意:给定 n 本书,让你分成 k 份,求出每一份的和中最小的最大值。最后以这个值,从后往前划分书本。

思路

  • 二分。单调性:和的最大值越大,能够分的份数越少。
  • 最小是趋势,最大值用来划分份数
  • check时,把累加不足 mid 的分为一份。返回份数。

所谓最大值也就是一个值而已,只不过它的是取了一个最大。我们对这个值(设为 x x x)进行二分即可。分析一下单调性,随着x的不断增大,份数k会不断减小。因此是递减的。
具体图像是这样的
在这里插入图片描述
对于分成k份的x值,不止一个,我们需要选一个最小的,因此不断往左逼近。此时取 check(mid)>k,能够得到的是恰好等于 k 的答案。

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const int maxn=500+10;
int t,n,k;
int a[maxn],visit[maxn];
int check(ll mid)
{
    ll sum=0;
    int seg=1;
    for(int i=1; i<=n; ++i)
        if(sum+a[i]<=mid) sum+=a[i];
        else sum=a[i],seg++;
    return seg;
}
void print(ll total)
{
    ll sum=0;
    int seg=1;
    for(int i=n; i>=1; --i)
    {
        if(sum+a[i]<=total&&k-seg<=i-1) sum+=a[i];
        else
        {
            sum=a[i];
            seg++;
            visit[i]=1;
        }
    }
    for(int i=1; i<=n; ++i)
    {
        printf("%d%c",a[i],i==n?'\n':' ');
        if(visit[i]) printf("/ ");
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        int L=0;
        ll sum=0;
        for(int i=1; i<=n; ++i)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
            L=max(L,a[i]);
            visit[i]=0;
        }
        ll R=sum;
        while(L<R)
        {
            int mid=(L+R)>>1;
            if(check(mid)>k) L=mid+1;
            else R=mid;
        }
        print(L);
    }
    return 0;
}

Vases and Flowers HDU - 4614(线段树+二分)

链接:http://acm.hdu.edu.cn/showproblem.php?pid=4614

题意:给定 n 个花瓶,m个操作。有两种类型:

  1. 1 A F:代表从第 A 个花瓶开始插 F 朵花(如果插不了F朵花,多余的花直接遗弃),如果能够插入至少一朵花,则输出插入的第一朵花和最后一朵花的位置。否则输出 “Can not put any one.”
  2. 2 A B:代表清空区间 [ A , B ] 上的所有花,并输出遗弃的花的数量

思路:叶节点维护是否为空瓶,根节点维护区间上空瓶的数量。对于操作1,先计算是否有空瓶,否则输出 “Can not put any one.” 。然后,在二分查找从第A个花瓶插入花的位置,只需找插入一朵花的位置,和最多能够插入的花的数量的位置即可。
对于这个二分方法,先分析它的单调性。,二分这个位置。起点是A,终点就是我们需要找的位置

图像如下
在这里插入图片描述

我们要找的这个num,其实是最小值。因为题目的要求是最后一个插花的位置,只有插了一朵花才能够让num增加
对于操作2,直接用Update清零即可

#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define ll long long
using namespace std;
const int maxn=5e4+10;
int t,n,m;
int st[maxn<<2],lazy[maxn<<2];
void pushUp(int rt)
{
    st[rt]=st[ls]+st[rs];
}
void pushDown(int rt,int L,int R)
{
    if(lazy[rt]!=-1)
    {
        if(lazy[rt]==0)
        {
            st[ls]=st[rs]=0;
            lazy[ls]=0;
            lazy[rs]=0;
        }
        else
        {
            int mid=(L+R)>>1;
            st[ls]=mid-L+1;
            st[rs]=R-mid;
            lazy[ls]=lazy[rs]=1;
        }
        lazy[rt]=-1;
    }
}
void build(int rt,int L,int R)
{
    lazy[rt]=-1;
    if(L==R)
    {
        st[rt]=1;
        return;
    }
    int mid=(L+R)>>1;
    build(ls,L,mid);
    build(rs,mid+1,R);
    pushUp(rt);
}
void update(int rt,int l,int r,int L,int R,int val)
{
    if(l<=L&&R<=r)
    {
        st[rt]=(R-L+1)*val;
        lazy[rt]=val;
        return;
    }
    pushDown(rt,L,R);
    int mid=(L+R)>>1;
    if(l<=mid) update(ls,l,r,L,mid,val);
    if(r>mid) update(rs,l,r,mid+1,R,val);
    pushUp(rt);
}
int query(int rt,int l,int r,int L,int R)
{
    if(l<=L&&R<=r)    return st[rt];
    pushDown(rt,L,R);
    int mid=(L+R)>>1;
    int ans=0;
    if(l<=mid) ans+=query(ls,l,r,L,mid);
    if(r>mid) ans+=query(rs,l,r,mid+1,R);
    return ans;
}
int search(int left,int num)
{
    int l=left,r=n;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(query(1,left,mid,1,n)<num) l=mid+1;
        else r=mid;
    }
    return l;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        build(1,1,n);
        int op,l,r,x;
        while(m--)
        {
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d%d",&l,&x);
                l++;
                int res=query(1,l,n,1,n);
                if(res==0) puts("Can not put any one.");
                else
                {
                    int ansl=search(l,1);
                    int ansr=search(l,min(res,x));
                    printf("%d %d\n",ansl-1,ansr-1);
                    update(1,ansl,ansr,1,n,0);
                }
            }
            else
            {
                scanf("%d%d",&l,&r);
                l++,r++;
                printf("%d\n",r-l+1-query(1,l,r,1,n));
                update(1,l,r,1,n,1);
            }
        }
        puts("");
    }
    return 0;
}

CF1288D Minimax Problem

链接:https://codeforces.com/contest/1288/problem/D

题面
在这里插入图片描述
题意:大致意思:给定 n 个数组,让你任选两个数组,对应取最大值,其中的最小值是这一次选择的结果。然后所有结果的最大值,就是答案。让你输出选择的两个数组是多少。

思路: 如果按照暴力的来,那么结果是 O ( n 2 ) O(n^2) O(n2) 的。注意到 m 很小只有 8

  • 二分答案。 x 越大,那么满足条件的选择越来越少。其实就是:满足和不满足这两种结果是刚好分布在答案的两边的。一边是满足条件的,另一边是不满足条件的。这样就可以二分答案了。
  • 此时需要考虑的是如何check才能降低复杂度。怎样才能让两个数组先对应取最大值,然后最小值是 mid 。
  • 可以这样想,mid 是在枚举答案。mid 就是最小值,那么每个数组相对 mid 只有 ≥ m i d \ge mid mid < m i d <mid <mid 的区别。那么就可以状压,用 01 来表示大小。最后只需要两个数组相或的值等于 (1<<m)-1就可以了(也就是全部都是 1 )。
  • 虽然状压了,但还是有 n 个数,任选两个的话,还是 O ( n 2 ) O(n^2) O(n2) 的复杂度。此时可以看到,这些数的值域范围很小,只有 [ 0 , 2 8 ] [0,2^8] [0,28] 。那么就可以记录每个值的 pos ,然后枚举两个访问过的数值就可以了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+10;

int n,m;
int a[maxn][10];
int visit[300];
int ans1,ans2;
bool check(int mid)
{
    memset(visit,0,sizeof(visit));
    for(int i=1; i<=n; ++i)
    {
        int value=0;
        for(int j=1; j<=m; ++j)
            if(a[i][j]>=mid) value|=(1<<j-1);
        visit[value]=i;
    }
    for(int i=0; i<(1<<m); ++i)
    {
        if(!visit[i]) continue;
        for(int j=0; j<(1<<m); ++j)
        {
            if(visit[j]&&(i|j)==(1<<m)-1)
            {
                ans1=visit[i];
                ans2=visit[j];
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; ++i)
        for(int j=1; j<=m; ++j)
            scanf("%d",&a[i][j]);
    int L=0,R=1e9;
    while(L<R)
    {
        int mid=(L+R+1)>>1;
        if(check(mid)) L=mid;
        else R=mid-1;
    }
    check(L);
    printf("%d %d\n",ans1,ans2);
    return 0;
}

CF1260D A Game with Traps

链接:https://codeforces.com/contest/1260/problem/D

题意:你和 m 个士兵在数轴 0 的位置,需要到达 n+1 的位置。每个士兵都有一个敏捷度 a i a_i ai 。数轴上有 k 个陷阱有三个属性 l i , r i , d i l_i,r_i,d_i li,ri,di d i d_i di 表示当士兵敏捷度小于 a j a_j aj 时会死亡。 l i l_i li 表示陷阱的位置, r i r_i ri 表示你可以到达 r i r_i ri 的位置关掉这个陷阱。你每移动一格会花费 1 秒的时间,问在 t t t 秒内,你最多能够带多少士兵达到 n + 1 n+1 n+1 这个点。 ( 1 ≤ m , n , k , t ≤ 2 ⋅ 1 0 5 , n < t , 1 ≤ l i ≤ r i ≤ n , 1 ≤ d i ≤ 2 ⋅ 1 0 5 ) (1≤m,n,k,t≤2⋅10^5, n<t,1≤l_i≤r_i≤n, 1≤d_i≤2⋅10^5) (1m,n,k,t2105,n<t,1lirin,1di2105)

思路

  • 可以发现带的士兵越多,那么需要的时间越多。
  • 二分答案。check时可以使用第 mid 大的士兵的敏捷度来计算通过这些关卡所需要的时间。
  • 按左区间将陷阱排序,维护当前士兵的位置 pos 和所需的时间 res。每次遇到一个 d i d_i di > a [ m i d ] a[mid] a[mid] 的陷阱时,可以将士兵移到 r i r_i ri。士兵相对于一个陷阱有三种位置: p o s ≤ l i − 1 , l i ≤ p o s l e r i , r i ≤ p o s pos\le l_i-1 ,l_i\le pos le r_i , r_i \le pos posli1,liposleri,ripos
    (1)当 p o s ≤ l i − 1 pos\le l_i-1 posli1时,先将 pos 变为 l i − 1 l_i-1 li1,然后在变为 r i r_i ri
    (2)当 l i ≤ p o s ≤ r i l_i\le pos \le r_i liposri 时,将 pos变为 r i r_i ri
    (3)当 r i ≤ p o s r_i \le pos ripos 时,直接跳过即可,因为由于前面陷阱的影响,这个陷阱已经被关掉了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
int m,n,k,t;
int a[maxn];
struct Trap
{
    int l,r,d;
    bool operator<(const Trap& b) const
    {
        return l<b.l;
    }
}traps[maxn];
bool check(int mid)
{
    int cur=a[m-mid+1],res=0;
    int pos=0;
    for(int i=1;i<=k;++i)
    {
        if(pos>=traps[i].r) continue;
        if(cur<traps[i].d)
        {
            if(pos<=traps[i].l-1)
            {
                res+=traps[i].l-1-pos;
                pos=traps[i].l-1;
            }
            res+=(traps[i].r-pos)*3;
            pos=traps[i].r;
        }
    }
    res+=n+1-pos;
    return res<=t;
}
int main()
{
    scanf("%d%d%d%d",&m,&n,&k,&t);
    for(int i=1;i<=m;++i) scanf("%d",&a[i]);
    sort(a+1,a+1+m);
    for(int i=1;i<=k;++i)
    {
        int l,r,d;
        scanf("%d%d%d",&l,&r,&d);
        traps[i]={l,r,d};
    }
    sort(traps+1,traps+1+k);
    int L=0,R=m;
    while(L<R)
    {
        int mid=(L+R+1)>>1;
        if(check(mid)) L=mid;
        else R=mid-1;
    }
    printf("%d\n",L);
    return 0;
}

CF1370D. Odd-Even Subsequence

链接:https://codeforces.com/contest/1370/problem/D

题意:定义一个序列 s 的花费为: m i n ( m a x ( s 1 , s 3 , s 5 , … ) , m a x ( s 2 , s 4 , s 6 , … ) ) . min(max(s1,s3,s5,…),max(s2,s4,s6,…)). min(max(s1,s3,s5,),max(s2,s4,s6,)).,给定一个长度为 n 的序列,请你选择一个长度为 k 的子序列使得序列的花费最小,问最小的花费是多少。

思路

  • 二分答案。check时,要想答案为 mid,有两种情况:1. 奇数位最大值为 mid,偶数位最大值大于等于 mid 。2. 偶数位最大值为 mid,奇数位最大值大于等于 mid
  • 拿情况 1 来讨论,也就是在奇数位取数时有限制,在偶数位取数任意(只需要保证 1 位 ≥ m i d \ge mid mid 即可)。因此可以对 a i a_i ai 和 mid 的大小关系状压到 b 。在 c 数组中存 01序列。后面判断 c [ p ] ≥ b [ i ] c[p]\ge b[i] c[p]b[i] ,c 数组中1 代表任意取,0 表示限制。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10,mod=1e9+7;

int n,k;
int a[maxn],b[maxn],c[maxn];
bool solve()
{
    int p=1;
    for(int i=1; i<=n; ++i)
        if(p<=k&&c[p]>=b[i]) p++;
    return p==k+1;
}
bool check(int mid)
{
    for(int i=1; i<=n; ++i) b[i]=a[i]>mid;
    for(int i=0; i<2; ++i)
    {
        for(int j=1; j<=k; ++j) c[j]=(i+j&1);
        if(solve()) return true;
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
    int L=1,R=1e9;
    while(L<R)
    {
        int mid=(L+R)>>1;
        if(check(mid)) R=mid;
        else L=mid+1;
    }
    printf("%d\n",L);
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值