整数二分:
题目链接:http://oj.daimayuan.top/course/22/problem/59
板子题:
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int a[N],n,q;
int calc1(int x){//查找第一个小于x的位置
int L=0,R=n+1;
while(L+1<R){
int mid=L+R>>1;
if(a[mid]<x) L=mid;
else R=mid;
}
return L;
}
int calc2(int x){//查找第一个小于等于x的位置
int L=0,R=n+1;
while(L+1<R){
int mid=L+R>>1;
if(a[mid]<=x) L=mid;
else R=mid;
}
return L;
}
inline void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
while(q--){
int x;cin>>x;
int x1=calc1(x),x2=calc2(x);
cout<<x1<<" "<<x2-x1<<" "<<n-x2<<"\n";
}
}
int main(){
solve();
}
题目链接:http://oj.daimayuan.top/course/22/problem/61
题意:给定一个长度为N的数组,以及最大操作次数K,求经过M次操作后使得数列中的最小值最大。
思路:要想使得最小值最大,那么一定是经过了K次操作,这里直接二分答案,通过答案来求解操作数
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int a[N],n,k;
bool check(int x){//检查答案是否符合条件
int ans=0;
for(int i=1;i<=n;i++)
ans+=max(0,x-a[i]);
return ans<=k;
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
int L=0,R=n+1;
while(L+1<R){//二分答案
int mid=L+R>>1;
if(check(mid)) L=mid;
else R=mid;
}
cout<<L;
}
题目链接:http://oj.daimayuan.top/course/22/problem/649
方法: 二分,怎么二分?
因为把所有序列给合并起来,再算和,是一定会TIE的。
如果可以求出每个序列都能满足条件的和,再把所有序列的和加起来,就好了。
那么我们找出了位置,如何求和呢?
直接采用等差数列的公式进行O(1)求和:n*(a1+an)/2
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int n,k[N],b[N],m;
int calc(int z){//计算边界L左边小于等于z的个数
int res=0;
for(int i=1;i<=n;i++){
if(z>=b[i]){
//kx+b=z--->x=(z-b)/k
int y=(z-b[i])/k[i]+1;//记录个数
//为什么要加1,是因为每组序列中,都存在x=0的情况
res+=y;//记录个数
}
}
return res;
}
int sum(int z){
int res=0;
for(int i=1;i<=n;i++){
if(z>=b[i]){
int y=(z-b[i])/k[i] + 1;//记录项数
res+=1LL*((b[i]+(y-1)*k[i]+b[i])*y/2);//记录每个序列中的和
//等差数列求和公式:n*(a1+an)/2
}
}
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>k[i]>>b[i];
cin>>m;
int L=0,R=1e14;//R尽可能的大
while(L+1<R){//二分
int mid=L+R>>1;
if(calc(mid)<=m) L=mid;
else R=mid;
}
int ans=sum(L)+1LL*((m-calc(L))*(L+1));//最终答案
cout<<ans;
}
浮点数二分:
这里稍微讲一下:浮点数二分其实和整数二分是差不多的。唯一不同点在于:整数的二分次数是不确定的,而浮点数二分一般是能够算出来的,但是浮点数二分一般二分100次就可以了。
题目链接:http://oj.daimayuan.top/course/22/problem/648
思路:二分答案。对于方向移动问题,我们可以考虑成区间的问题解答。
举个例子:a b人在两个不同点,他们要达到目标点,给定两人的速度,问最少能够到达?
我们发现,只要两人跑到路程区间有交集,那么就可以到达,最少时间为跑的慢的人刚好到达目标点的时间。
那么为什么跑的快的人不会超过目标位置呢?
因为跑的快的人到达目标点后,是可以原地踏步的,就相当于在原地跑。(奇怪的知识又增加了Q_Q)
同理:多人跑目标位置,也是一样的道理。每一次都要改变交集,以保持最优情况
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n;
int pos[N],v[N];
bool check(double x){
double L=-1e10,R=1e10;//初始区间尽可能大
for(int i=1;i<=n;i++){
double l=pos[i]-v[i]*x,r=pos[i]+v[i]*x;//求左右距离
L=max(L,l);R=min(R,r);//求交集
}
if(L>R) return false;//若无交集
return true;//有交集
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>pos[i]>>v[i];
double L=0,R=5e4;//定义二分时间的区间//需根据题意自行判断区间
for(int i=1;i<=100;i++){//二分次数100次
double m=(L+R)/2;
if(check(m)) R=m;
else L=m;
}
cout<<fixed<<setprecision(10)<<L;//输入L,R都可以
}
二分+哈希求字符串最长回文子串的长度,我们秉着最优的方法,这里就不讲了。
二分+哈希:O(nlogn) manacher:O(n)
题目链接:http://oj.daimayuan.top/course/22/problem/698
二分求Lis(最长上升子序)也不讲了。秉着最优原则