二分
二分
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 A F:代表从第 A 个花瓶开始插 F 朵花(如果插不了F朵花,多余的花直接遗弃),如果能够插入至少一朵花,则输出插入的第一朵花和最后一朵花的位置。否则输出 “Can not put any one.”
- 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) (1≤m,n,k,t≤2⋅105,n<t,1≤li≤ri≤n,1≤di≤2⋅105)
思路:
- 可以发现带的士兵越多,那么需要的时间越多。
- 二分答案。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
pos≤li−1,li≤posleri,ri≤pos
(1)当 p o s ≤ l i − 1 pos\le l_i-1 pos≤li−1时,先将 pos 变为 l i − 1 l_i-1 li−1,然后在变为 r i r_i ri
(2)当 l i ≤ p o s ≤ r i l_i\le pos \le r_i li≤pos≤ri 时,将 pos变为 r i r_i ri
(3)当 r i ≤ p o s r_i \le pos ri≤pos 时,直接跳过即可,因为由于前面陷阱的影响,这个陷阱已经被关掉了
#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;
}