二分的基本用途是在单调序列或单调函数中做查找操作
三分解决单调函数极值及相关问题,例如凸性函数,二次函数就是,利用函数的单峰性,如果函数不严格单调,那就不能适用
一本通题目
一、取余运算:输入b<span id="MathJax-Span-2" class="mrow"><span id="MathJax-Span-3" class="mi">,p,k 的值,求b^pmodk 的值。其中b、p、k长整型数。
就是快速幂
int modd(int x){ //代入的是次数 if(x==0) return 1; //这个一定的要 int tmp=modd(x/2)%k; //注意逻辑关系 tmp=tmp*tmp%k; if(x%2==1) tmp=(tmp*b)%k; return tmp; }
快速幂的迭代写法
LL ans=1; LL a,b,m; while(b>0){ if(b&1){ ans=ans*a%m; } a=a*a%m; b>>=1; }
二、网线主管
给定N,K,为绳子的个数和需要的数量,然后在给定N个绳子的长度,求能够切割成K个,且切割后长度最大的长度为多少,这道题其实没有想象的复杂,分治就好了
,但是要注意对 细节的处理,double、int类型的处理
double a[10001]; int b[10001],n,m,l,r; bool judge(int x){ int ans=0; for(int i=1;i<=n;i++) ans+=b[i]/x; //都是转换为了整数进行运算 return ans>=m; } int main(){ cin>>n>>m; l=r=0; for(int i=1;i<=n;i++){ cin>>a[i]; b[i]=(int)(a[i]*100+0.5); //写法!!!(int)(x+0.5) if(b[i]>r) r=b[i]; } r+=1; //用分治来选择,这里r+1!!!! while(l+1<r){ int mid=(l+r)/2; if(judge(mid)) l=mid; else r=mid; } printf("%.2lf\n",l/100.0);
三、月度开销
int a[100001],n,m,sum=0; int l,r ; bool judge(int x){ int c=0,b=0,i; for(i=1;i<=n;i++){ b+=a[i]; if(b>=x){ c++; if(a[i]<x) b=a[i]; else return 1;//a[i]>=x左边界left=mid;嗯。。不太懂 ------其实是如果一个单个的天开销都比划定的月度开销大,那么肯定阅读开销应该更大才对 //所以返回1,然l改为mid,把月度开销调大(666) } } return c>=m; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>a[i]; sum+=a[i]; } l=1; r=sum; //注意这里的r选择,,,是选的月度开销 while(l+1<r){ int mid=(l+r)/2; if(judge(mid)) l=mid; else r=mid; } if(judge(l)) cout<<l<<endl; else cout<<r<<endl; return 0; }
四、和为给定数,简单,two pointers
int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; int left=1,right=n; cin>>m; sort(a+1,a+n+1); while(left<right){ if(a[left]+a[right]==m){ cout<<a[left]<<" "<<a[right]<<endl;//找到了就退出,这个其实就是two pointers的思想 break; } else if(a[left]+a[right]>m) right--; else left++; } if(left==right) cout<<"No"<<endl; return 0; }
五、河中跳房子
cin>>s>>n>>m; for(int i=1;i<=n;i++) cin>>dis[i]; dis[0]=0; dis[n+1]=s; int i=0,j,ans=0; l=0;r=s+1;//左边右边赋值 //枚举的是距离!!只要超过这个距离就移除 while(l+1<r){ int mid=(l+r)/2; ans=0; i=0; while(i<=n){ j=i+1; while(j<=n+1&&dis[j]-dis[i]<mid) j++; //主义者里面的写法 ans+=j-i-1;//注意还要减一 i=j;//这里不能减了就移动i; } //上面这个while是用来计算这次的Mid能够移去多少块石头 if(ans<=m) l=mid; //如果少了,就说明mid太小了,不行就只能把left置为mid,让mid变大 else r=mid; } cout<<l<<endl;
六、2012,给定一个最多为200位的数N,求2012^N的最后四位
char a[N]; int main() { int k; cin>>k; while(k--) { cin>>a; int len=strlen(a); int B=0,C=2011; int i; for(i=len-4;i<len;i++) if(i>=0) B=B*10+a[i]-'0'; i=1; do{ if(i*2<=B) { i*=2; C=(C*C)%10000; } }while(i*2<=B); for(;i<B;i++) C=(C*2011)%10000; cout<<C<<endl; } return 0; }
一本通提高篇
1433:【例题1】愤怒的牛
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //排序之后,通过二分距离,找到距离最接近放下 int n,c; int a[maxn]; bool check(int d){ int cow=1; int now=a[1]+d; for(int i=2;i<=n;i++){ if(a[i]<now) continue; cow++; now=a[i]+d; } return cow>=c; } int main(){ scanf("%d %d",&n,&c); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } sort(a+1,a+1+n); int l=0,r=a[n]-a[1]; //!!!二分 while(l<=r){ int mid=(l+r)/2; if(check(mid)) l=mid+1; else r=mid-1; } printf("%d\n",r); return 0; }
1434:【例题2】Best Cow Fences
让序列每个值减去平均值(模拟的mid),判断这个长度不小于L的子序列和是否大于还是小于0,注意!! r-l>eps eps=1e-5
初始值也是 l=1e-6,r=1e6
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn= 100010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //1.是否存在一个长度不小于L的子段,子段序列和为非负。及二分查找 //2.子段序列和可以是后段减前段 double a[maxn],b[maxn],summ[maxn]; const double ex=1e-5; int n,L; int main(){ scanf("%d %d",&n,&L); for(int i=1;i<=n;i++){ scanf("%lf",&a[i]); } double l=1e-6,r=1e6,mid; while(r-l>ex){ mid=(l+r)/2; for(int i=1;i<=n;i++) b[i]=a[i]-mid; for(int i=1;i<=n;i++) summ[i]=(summ[i-1]+b[i]); double ans=-INF; double minn=INF; for(int i=L;i<=n;i++){ minn=min(minn,summ[i-L]); ans=max(ans,summ[i]-minn); //前缀和相减 } if(ans>=0) l=mid; else r=mid; } printf("%d\n",int(r*1000)); return 0; }
1435:【例题3】曲线
二次函数求极值:三分
eps搞得很小,反正管的他的
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn= 1e5+100; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int t,n; //三分,因为形成的是凸函数 double a[maxn],b[maxn],c[maxn]; double eps=1e-9; double js(double x){ double maxx=-INF; for(int i=0;i<n;i++){ maxx=max(maxx,a[i]*x*x+b[i]*x+c[i]); } return maxx; } int main(){ scanf("%d",&t); while(t--){ scanf("%d",&n); for(int i=0;i<n;i++){ scanf("%lf %lf %lf",&a[i],&b[i],&c[i]); } double l=0,r=1000,lm,rm; while(fabs(r-l)>eps){ lm=l+(r-l)/3.0; rm=r-(r-l)/3.0; if(js(lm)>js(rm)) l=lm; else r=rm; } printf("%.4lf\n",js(r)); } return 0; }
1436:数列分段II
对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=100001; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int n,m; int a[maxn]; /* 二分答案 我们最终答案的取值区间是[ max(a[i]) , ∑a[i] ] 设定 l=max(a[i]) , r=∑a[i] , mid不断二分 mid表示每段和的最大值,也就是每段和都不超过mid 放到check函数里,计算一下在mid为最大值的情况下可以分成多少段 如果段数 cnt > m ,说明这个mid小了,它还可以再大一点 如果段数 cnt <= m , 说明这个mid大了,那么它就要小一点了,由于此时cnt可能等于m,这个mid为候选答案,记录下来(如果他是真正答案,最后输出的就是他,否则他会被更新为一个更小的) */ int check(int x){ int ans=1; int tmp=a[1]; for(int i=2;i<=n;i++){ if(tmp+a[i]<=x){ tmp+=a[i]; } else{ ans++; tmp=a[i]; } } return ans<=m; } int main(){ scanf("%d %d",&n,&m); int l=-INF,r=0; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); l=max(l,a[i]); r+=a[i]; } int mid,ans; while(l<=r){ mid=(l+r)/2; //cout<<l<<" "<<r<<endl; if(check(mid)){ ans=mid; r=mid-1; } else l=mid+1; } printf("%d",ans); return 0; }
1437:扩散
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=52; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //没见过世面TATTAT。。。。。 //数学知识 /* 两个点a、b连通,记作e(a,b),当且仅当a、b的扩散区域有公共部分。 连通块的定义是块内的任意两个点u、v都必定存在路径e(u,a0),e(a0,a1),…,e(ak,v)。 也就是说一个点扩散到另一个点的 时间 就是他们的横纵坐标差值的和(xi-xj)+ ( yi-yi ) 如果 时间<mid2 就说明两个块连通(注意mid要2,因为两个点同时扩散) 然后用并查集储存连通块 */ //https://blog.csdn.net/justidle/article/details/104355432 struct node{ int x,y; }nodes[maxn]; int dis[maxn][maxn]; //曼哈顿距离 int spread[maxn]; //上一次的扩散位置 bool check(int x,int n){ //根据扩散时间x,遍历所有点,查找是否存在连通 bool vis[maxn]={}; int step=0; int tim=1; //看在这前有没有交集 spread[1]=1; vis[1]=1; while(step!=tim){ step++; for(int i=1;i<=n;i++){ if(vis[i]) continue; //已经访问,则跳过 //每次取中间点 mid 后,从任意一个点出发尝试遍历所有点。a 能达到 b 的条件一定是 a 到 b 的曼哈顿距离不超过 2 倍的 mid 值。如果能成功遍历所有点,说明当前 mid 符合条件;反之,则不符合条件。 if(dis[spread[step]][i]<=2*x) { vis[i]=1; spread[++tim]=i; } } } return tim==n; } int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d %d",&nodes[i].x,&nodes[i].y) ; } for(int i=1;i<n;i++){ for(int j=i+1;j<=n;j++){ dis[i][j]=dis[j][i]=abs(nodes[i].x-nodes[j].x)+abs(nodes[i].y-nodes[j].y); } } int l=0,r=1e9,mid; //符合条件,右边界移动,我们尝试更小 mid 的可能性。如果不符合条件,左边界移动,我们尝试更大 mid 的可能性。 //直到不符合二分查找条件,即 left ≤ right,不成立。right 就是我们需要的答案。 while(l<=r){ mid=(l+r)/2; if(check(mid,n)) r=mid-1; else l=mid+1; } printf("%d\n",l); return 0; }
1438:灯泡
这道题都可以算是数学题了,主要用等比三角形算出表达式,然后根据式子单调用三分
(37条消息) 一本通题解——1438:灯泡_努力的老周的博客-CSDN博客
#include <bits/stdc++.h> using namespace std; double H, h, D;//由于check函数需要用的,就用全局变量吧 double check(double x) { if ((H-h)/x*(D-x)>h) { //灯的高度小于影子到墙底部距离。属于第二种情况 return x/(H-h)*h; } else { //属于第一种情况 return D-x+h-(H-h)/x*(D-x); } } int main() { int t; cin>>t; int i, j; for (i=0; i<t; i++) { cin >> H >> h >> D; //三分查找 double eps = 1e-8; double left=0; double right=D; double l_mid, r_mid; while (left+eps<right) { l_mid = left+(right-left)/3; r_mid = right-(right-left)/3; if (check(l_mid) > check(r_mid)) { right = r_mid; } else { left = l_mid; } } printf("%.3lf\n", check(left)); } return 0; }
1439:【SCOI2010】传送带
这个是两层三分
首先要假装确定E点位置,然后三分F点,然后再三分E点的时候就可以真的确定E点
#include<bits/stdc++.h> using namespace std; const double eps=1e-8; //我一般喜欢把eps设为1e-8,当然对于这题1e-6足够了 double ax,ay,bx,by,cx,cy,dx,dy,p,q,r; double dis(double x_1,double y_1,double x_2,double y_2){ //P1和P2的距离 double xdis=x_1-x_2,ydis=y_1-y_2; return sqrt(xdis*xdis+ydis*ydis); } double f(double x_1,double y_1,double x_2,double y_2){ //上文中提到的f(P1)在当F是P2时的值 return dis(x_1,y_1,x_2,y_2)/r+dis(x_2,y_2,dx,dy)/q; //第一个是dis(E,F)/r,第二个是dis(F,D)/q } double calc1(double x,double y){ //内层三分F(这个给定的参数是固定E是这个点) double lx=cx,ly=cy,rx=dx,ry=dy; //在CD上三分 while(dis(lx,ly,rx,ry)>eps){ //还没有重合成一点 double tmpx=(rx-lx)/3,tmpy=(ry-ly)/3; double lmidx=lx+tmpx,rmidx=rx-tmpx,lmidy=ly+tmpy,rmidy=ry-tmpy; //(lmidx,lmidy)是左等分点,(rmidx,rmidy)是右等分点 double ans1=f(x,y,lmidx,lmidy),ans2=f(x,y,rmidx,rmidy); //左等分点和右等分点的值 if(ans2-ans1>eps) rx=rmidx,ry=rmidy; //左边更小,舍弃右边 else lx=lmidx,ly=lmidy; //右边更小,舍弃左边 } return f(x,y,lx,ly); //返回这个最小值 } double calc(){ //外层三分E double lx=ax,ly=ay,rx=bx,ry=by; //在AB上三分 while(dis(lx,ly,rx,ry)>eps){ double tmpx=(rx-lx)/3,tmpy=(ry-ly)/3; double lmidx=lx+tmpx,rmidx=rx-tmpx,lmidy=ly+tmpy,rmidy=ry-tmpy; //以上同理 double ans1=calc1(lmidx,lmidy)+dis(ax,ay,lmidx,lmidy)/p,ans2=calc1(rmidx,rmidy)+dis(ax,ay,rmidx,rmidy)/p; //左等分点和右等分点的值(在这里套了内层三分) if(ans2-ans1>eps) rx=rmidx,ry=rmidy; else lx=lmidx,ly=lmidy; //同理 } return calc1(lx,ly)+dis(ax,ay,lx,ly)/p; //返回最小值,也就是答案 } int main(){ scanf("%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",&ax,&ay,&bx,&by,&cx,&cy,&dx,&dy,&p,&q,&r); printf("%.2lf\n",calc()); //这就是答案 }