二分、三分专题——经典题型略解
好久没写blog了嗷。
挑战程序设计竞赛上二分的标题就是——不光是查找值。所以在这里总结一下上一周的二分三分训练。
零、有序数组中查找某个值(不说了)
一、最大化最小值
POJ2456、POJ3258 这两个题非常像嗷,稍微改下代码就可以了嗷。
我先做的3258.
2456:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 #define maxn 50050 6 int l,n,m; 7 int dis[maxn]; 8 9 bool C(int x){ 10 int last=0; 11 for(int i=1;i<m;i++){//m-1次 12 int cur=last+1; 13 while(cur<n&&dis[cur]-dis[last]<x) 14 cur++; 15 if(cur==n) return 0; 16 17 last=cur; 18 } 19 return 1; 20 } 21 int main(){ 22 scanf("%d%d",&n,&m); 23 for(int i=0;i<n;i++) scanf("%d",dis+i); 24 sort(dis,dis+n); 25 26 int le=0,ri=dis[n-1]; 27 while(ri-le>1){ 28 int mid=(ri+le)/2; 29 30 if(C(mid)) le=mid;//如果放得开,说明x还可以更大 31 else ri=mid; 32 } 33 printf("%d\n",le); 34 return 0; 35 }
3258:
1 /* 2 一条长L的河上,除了0 和 l 处还有N 个石子,分别距离起点距离di, 3 求去掉M个石子后相邻的最小距离的最大值。(最大化最小值) 4 和poj2456基本一样 2456是给每个点的位置,这个题还要加上一个从0开始,到l处结束,相当于多两个元素 5 6 定义C(x):可以去掉m个石头使任意石头间距不小于x。 7 */ 8 9 #include <iostream> 10 #include <cstdio> 11 #include <algorithm> 12 using namespace std; 13 #define maxn 50050 14 int l,n,m; 15 int dis[maxn]; 16 17 bool C(int x){ 18 int num=n-m; 19 int last=0; //删除num个石头,循环num次 但2456里选择m个点只需循环m-1次,一开始想不明白wa了很久 20 for(int i=0;i<num;i++){//对于这些石头,要使任意间距不小于x, 21 int cur=last+1; 22 while(cur<=n&&dis[cur]-dis[last]<x)//就要把下一个放入第一个不满足while条件的位置 23 cur++; //由cur记录 24 if(cur>n) return 0;//如果在这个过程中大于n了,说明放不开 25 26 last=cur;//更新检查完的位置 27 } 28 return 1; 29 } 30 int main(){ 31 scanf("%d%d%d",&l,&n,&m); 32 if(n==m) { 33 printf("%d\n",l); 34 return 0; 35 } 36 for(int i=1;i<=n;i++) scanf("%d",dis+i); 37 dis[n+1]=l; 38 sort(dis,dis+n+2); 39 40 int le=0,ri=l; 41 while(ri-le>1){ 42 int mid=(ri+le)/2; 43 44 if(C(mid)) le=mid;//如果放得开,说明x还可以更大 45 else ri=mid; 46 } 47 printf("%d\n",le); 48 return 0; 49 }
二、假定一个解判断是否可行/是否满足条件
POJ1759、POJ3104、POJ1064(坑)
其实1759看网上别人的blog还有依次算、依次满足的方法,在这个题里也很快。
1759二分:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 #define inf 0x3f3f3f3f 5 #define maxn 1006 6 #define eps 1e-8// 7 int n; 8 double A,num[maxn]; 9 bool solve(double mid){ 10 num[1]=mid; 11 for(int i=2;i<n;i++){ 12 num[i]=2*num[i-1]+2-num[i-2]; 13 if(num[i]<eps) return false; //写小于0由于精度问题会wa 14 } 15 return true; 16 } 17 int main(){ 18 while(scanf("%d%lf",&n,&A)==2){ 19 num[0]=A; 20 double low=-inf; 21 double high=inf; 22 for(int i=0;i<100;i++){ 23 double mid=(low+high)/2; 24 if(solve(mid)) high=mid; 25 else low=mid; 26 } 27 printf("%.2lf\n",num[n-1]); 28 } 29 return 0; 30 }
1759另解:
1 /* 2 H1 = a, H3 = H2 * 2 + 2 - H1, H4 = H3 * 2 + 2 - H2, ..... 3 Hn = Hn-1 * 2 + 2 - Hn-2 4 每个式子都能化简为k * H2 + b的形式 5 */ 6 #include <bits/stdc++.h> 7 using namespace std; 8 #define maxn 1005 9 10 int n,k[maxn]; 11 double a,b[maxn],h2; 12 double solve(){ 13 double h2=0; 14 k[1]=0, b[1]=a, k[2]=1, b[2]=0.0;//H1=0 * H2 + a, H2=1 * H2 + 0 15 for (int i = 3; i <= n; i ++) { 16 k[i] = 2 * k [i - 1] - k[i - 2]; 17 b[i] = 2 * b[i - 1] - b[i - 2] + 2; 18 if (h2 * k[i] + b[i] < 0) 19 h2 = -b[i] / k[i];//令此式==0解出h2, 可使h2尽量小, 最后乘出来的hn也就最小 20 } 21 return h2*k[n]+b[n]; 22 } 23 int main() { 24 while (~scanf("%d%lf", &n, &a)) { 25 printf("%.2lf\n", solve()); 26 } 27 return 0; 28 }
3104:
1 /* 2 1、对于一件ai值小于等于mid的衣服,直接晾干即可; 3 2、对于一件ai值大于mid值的衣服,最少的用时是用机器一段时间,晾干一段时间 4 C(mid):所有衣物都能干的最短时间mid 5 设这两段时间分别是x1和x2,那么有mid=x1+x2,ai<=k*x1+x2, 6 解得x1>=(ai-mid)/(k-1) ,所以对(ai-mid)/(k-1)向上取整就是该件衣服的最少用时。 7 */ 8 //#include<bits/stdc++.h> 9 #include <iostream> 10 #include <cstdio> 11 #include <cmath> 12 using namespace std; 13 #define ll long long 14 long long a[100005];//用int会WA 15 int n; 16 ll k; 17 bool C(ll x){ 18 ll sum = 0; 19 for (int i = 0; i < n; i++) 20 if (a[i] > x) 21 sum += ceil ( (a[i] - x) * 1.0 / (k - 1));//所有衣服晾干需要的总时间 22 23 return sum>x; 24 } 25 26 int main() { 27 scanf ("%d", &n); 28 ll maxx = -1; 29 for (int i = 0; i < n; i++) { 30 scanf ("%lld", a+i); 31 maxx=max(maxx,a[i]);// 找出含水量的最大值作为上界 32 } 33 scanf ("%lld", &k); 34 if (k == 1)//k-1作除数会re 35 printf ("%lld\n", maxx); 36 else { 37 ll left = 1, right = maxx, mid; 38 while (right > left) { 39 mid = (left + right) / 2; 40 41 if (C(mid)) left = mid+1; 42 else right= mid; 43 } 44 printf ("%lld\n", left); 45 } 46 return 0; 47 }
三、最大化平均值
POJ2976
2976:
1 ///C(x):a[i]-x*b[i]从大到小排列前n-k个的和大于等于零 2 //#include <bits/stdc++.h> 3 #include <iostream> 4 #include <cstdio> 5 #include <algorithm> 6 using namespace std; 7 #define maxn 1005 8 int n,k,a[maxn],b[maxn]; 9 double y[maxn];//a[i]-x*b[i] 10 bool C(double x){ 11 for(int i=0;i<n;i++) y[i]=a[i]-x*b[i]; 12 sort(y,y+n); 13 14 double sum=0; 15 for(int i=0;i<n-k;i++) sum+=y[n-i-1]; 16 17 return sum>=0; 18 } 19 int main(){ 20 while(scanf("%d%d",&n,&k)&&(n||k)){ 21 for(int i=0;i<n;i++) scanf("%d",a+i); 22 for(int i=0;i<n;i++) scanf("%d",b+i); 23 24 double l=0,r=1000; 25 for(int i=0;i<100;i++){ 26 double mid=(l+r)/2; 27 if(C(mid)) l=mid; 28 else r=mid; 29 } 30 printf("%.0lf\n",100.0*l);//四舍五入 31 } 32 return 0; 33 }
四、导数求值
HDU2899、HDU1724
2899:
1 #include <iostream> 2 #include<cstdio> 3 #include<cmath> 4 const double eps = 1e-8; 5 using namespace std; 6 double hs(double x,double y){//原函数 7 return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-y*x; 8 } 9 double ds(double x,double y){//导函数 10 return 42*pow(x,6)+48*pow(x,5)+21*pow(x,2)+10*pow(x,1)-y; 11 } 12 int main(){ 13 int a; 14 scanf("%d",&a); 15 while(a--){ 16 double b,l,r,mid; 17 scanf("%lf",&b); 18 l=0.0; r=100.0; 19 while(r-l>eps){ 20 mid=(l+r)/2; 21 if(ds(mid,b)>0) r=mid; 22 else l=mid; 23 } 24 printf("%.4lf\n",hs(l,b)); 25 } 26 return 0; 27 }
1724:用到了辛普森公式,建议补充数学知识。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define ll long long 5 const double eps = 1e-10;//1e-8 WA了 6 double a, b; 7 double f (double x) { 8 return b * sqrt (1.0 - (x * x) / (a * a)); 9 } 10 double simpson (double l, double r) { 11 return (f (l) + 4.0 * f ( (l + r) / 2.0) + f (r)) / 6.0 * (r - l); 12 } 13 double integral (double l, double r) { 14 double mid = (l + r) / 2.0; 15 double res = simpson (l, r); 16 if (fabs (res - simpson (l, mid) - simpson (mid, r)) < eps) return res; 17 else return integral (l, mid) + integral (mid, r); 18 } 19 20 int main() { 21 int T; scanf("%d",&T); 22 double l, r; 23 while (T--) { 24 scanf("%lf%lf%lf%lf",&a,&b,&l,&r); 25 printf("%.3lf\n",2 * integral (l, r)); 26 } 27 return 0; 28 }
五、三分(挖坑)
二分要解决的问题是不严格单调序列,但是遇到如单峰函数或单谷函数这样的情况,就要用三分了。
以找单峰函数f(x)最大值为例:
1 double fun(double x){ 2 //f(x) 3 } 4 5 double tri_search(double left, double right){ 6 double midl, midr; 7 while (right-left > eps){ 8 midl = (left + right) / 2.0; 9 midr = (midl + right) / 2.0; 10 // 如果是求最小值的话这里判<=即可 11 if(fun(midl) >= fun(midr)) right = midr; 12 else left = midl; 13 } 14 return left; 15 }
不过这只是最简单的形式,一般题目里还是要写一个判断条件的函数,再推一个凸函数或凹函数公式,然后依据这个条件对这个函数来做三分。
POJ3296、POJ3737
详细看人家写的嗷:https://blog.csdn.net/pi9nc/article/details/9666627
Stay Hungry, Stay Foolish