二分
二分有两种:二分答案和二分查找;一般来说二分查找没什么意思,主要还是二分答案。
聪明的质检员:https://www.luogu.org/problemnew/show/P1314
题意概述:
题目给定了n个矿石,有价值和重量,m个区间$[L_i,R_i]$,自选一个参数W,对于每个区间求校验值并相加,为总校验值。求一个最优的$W$使得总校验值与标准值(给定)最接近。
如何计算校验值?区间内质量超过W的矿石数量与这些矿石的价值之和的乘积。
首先呢...W越大,满足条件的矿石数量就越多,价值之和就越大,校验值自然也越大啦,所以可以二分。因为我二分的知识十分匮乏...所以想了一个比较适合二分蒟蒻的做法:首先二分一个最大的小于标准值的校验值,再二分一个最小的大于标准值的校验值,取一个min。二分答案之后怎么check呢?用前缀和就好了。时间复杂度$O(log(maxw)(n+m))$
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # define R register int 5 6 using namespace std; 7 8 const int maxn=200005; 9 int n,m,l,r; 10 long long s,ans,t,S[maxn],cnt[maxn]; 11 int w[maxn],v[maxn],maxw,minw,mid; 12 struct nod 13 { 14 int l,r; 15 }a[maxn]; 16 17 int read() 18 { 19 int x=0,f=1; 20 char c=getchar(); 21 while (!isdigit(c)) 22 { 23 if(c=='-') f=-f; 24 c=getchar(); 25 } 26 while (isdigit(c)) 27 { 28 x=(x<<3)+(x<<1)+(c^48); 29 c=getchar(); 30 } 31 return x*f; 32 } 33 34 long long ab (long long a) 35 { 36 if(a<0) return -a; 37 return a; 38 } 39 40 long long check (int W) 41 { 42 memset(S,0,sizeof(S)); 43 memset(cnt,0,sizeof(cnt)); 44 for (int i=1;i<=n;++i) 45 { 46 if(w[i]>=W) S[i]=v[i],cnt[i]=1; 47 S[i]+=S[i-1]; 48 cnt[i]+=cnt[i-1]; 49 } 50 long long ans=0; 51 for (int i=1;i<=m;++i) 52 ans+=(S[ a[i].r ]-S[ a[i].l-1 ])*(cnt[ a[i].r ]-cnt[ a[i].l-1 ]); 53 return ans; 54 } 55 56 int main() 57 { 58 scanf("%d%d%lld",&n,&m,&s); 59 w[1]=read(),v[1]=read(); 60 maxw=minw=w[1]; 61 for (R i=2;i<=n;i++) 62 w[i]=read(),v[i]=read(),maxw=max(maxw,w[i]),minw=min(minw,w[i]); 63 for (R i=1;i<=m;i++) 64 a[i].l=read(),a[i].r=read(); 65 l=minw,r=maxw; 66 ans=check(l); 67 while (l<=r) 68 { 69 mid=(l+r)>>1; 70 t=check(mid); 71 if(t<s) r=mid-1; 72 else { l=mid+1; ans=min(ans,ab(t-s)); } 73 } 74 l=minw,r=maxw; 75 while (l<=r) 76 { 77 mid=(l+r)>>1; 78 t=check(mid); 79 if(t>s) l=mid+1; 80 else { r=mid-1; ans=min(ans,ab(t-s)); }; 81 } 82 printf("%lld",ans); 83 return 0; 84 }
Best Cow Fences:https://loj.ac/problem/10012
题意概述:给定一个长度为$n$的非负整数序列$A$,找出一个平均数最大的,长度不小于$L$的子段,输出这个平均值。
这道题乍一看没有什么思路,仔细想一下发现是一道$0-1$分数规划;
$0-1$分数规划这个算法我没有专门学过,类似于一种二分化式子的思想?
这道题的答案是满足单调性的,显然平均数大于$2$肯定更大于$1.$二分之后$check$:把所有数字都减去这个平均值,那么如果一段的和是整数就说明它的平均值大于等于二分值.找长度大于等于$L$的最大子段和就很简单啦.
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <string> 5 # include <algorithm> 6 # include <cmath> 7 # define R register int 8 # define ll long long 9 10 using namespace std; 11 12 const int maxn=100005; 13 int n,L,l,r,mid,ans; 14 int mi,ma,a[maxn]; 15 ll s[maxn]; 16 17 bool check (int x) 18 { 19 ll minn=0; 20 for (R i=1;i<=n;++i) 21 s[i]=s[i-1]+a[i]-x; 22 for (R i=L;i<=n;++i) 23 { 24 if(i-L>=0) minn=min(minn,s[i-L]); 25 if(s[i]>=minn) return true; 26 } 27 return false; 28 } 29 30 int main() 31 { 32 scanf("%d%d",&n,&L); 33 scanf("%d",&a[1]); 34 a[1]*=1000; 35 mi=ma=a[1]; 36 for (R i=2;i<=n;++i) 37 scanf("%d",&a[i]),a[i]*=1000,mi=min(mi,a[i]),ma=max(ma,a[i]); 38 l=mi,r=ma; 39 while(l<=r) 40 { 41 mid=(l+r)>>1; 42 if(check(mid)) 43 ans=mid,l=mid+1; 44 else 45 r=mid-1; 46 } 47 printf("%d",ans); 48 return 0; 49 }
最小圈:https://www.lydsy.com/JudgeOnline/problem.php?id=1486
题意概述:在一个有向图中找一个平均值最小的环.
说实话这道题我一直不大想写,因为它实在是有点假.嗯,非常假.太假了!
和上边那个题其实差不多的,都是$0-1$分数规划;
二分答案之后全部减掉,然后找负环.找负环.找负环.这道题网上的大多数题解竟然都是拿$dfs-spfa$过的!而且只要用$spfa$这算个什么题目.
不过这题也是有理论正解的,偷偷贴一个$rqy$的博客好了:https://www.cnblogs.com/y-clever/p/7043553.html;
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <queue> 5 # include <cstring> 6 # include <string> 7 # define pac make_pair 8 # define R register int 9 # define ll long long 10 11 using namespace std; 12 13 const double eps=0.000000001; 14 const int maxn=3003; 15 const int maxm=10004; 16 int h,n,m,firs[maxn],x,y,in_que[maxn],vis[maxn]; 17 double l=-1,r,mid,ans,d[maxn],z; 18 bool f=false; 19 struct edge 20 { 21 int too,nex; 22 double co; 23 }g[maxm+maxn]; 24 25 void add (int x,int y,double z) 26 { 27 g[++h].too=y; 28 g[h].nex=firs[x]; 29 firs[x]=h; 30 g[h].co=z; 31 } 32 33 void dfs (int x,double k) 34 { 35 vis[x]=true; 36 int j; 37 for (R i=firs[x];i;i=g[i].nex) 38 { 39 j=g[i].too; 40 if(d[x]+g[i].co-k<d[j]) 41 { 42 if(f) return; 43 if(vis[j]) 44 { 45 f=true; 46 return ; 47 } 48 d[j]=d[x]+g[i].co-k; 49 dfs(j,k); 50 } 51 } 52 vis[x]=false; 53 } 54 55 bool check (double k) 56 { 57 memset(d,0,sizeof(d)); 58 memset(vis,0,sizeof(vis)); 59 d[0]=0; 60 f=false; 61 dfs(0,k); 62 return f; 63 } 64 65 int main() 66 { 67 scanf("%d%d",&n,&m); 68 for (R i=1;i<=m;++i) 69 { 70 scanf("%d%d%lf",&x,&y,&z); 71 add(x,y,z); 72 } 73 for (R i=1;i<=n;++i) 74 add(0,i,0); 75 r=1e5,l=-1e5; 76 ans=r; 77 while (r-l>=-eps) 78 { 79 mid=(l+r)/2.0; 80 if(check(mid)) 81 ans=min(ans,mid),r=mid-eps; 82 else 83 l=mid+eps; 84 } 85 printf("%.8lf",ans); 86 return 0; 87 }