常用技巧:二分搜尋
阿新 • • 發佈:2020-10-09
二分不僅是簡單的一個查詢工具,而且是一種型別題目的解體思路。困在了一道題上:求最大值的最小(大概是這種型別......)遇到這種情況應該考慮二分查詢。先將可能的最大值列出來,對該數列排序,在該數列上進行二分,判斷當前節點是否合適,由此求出最大值的最小值。
例題洛谷P1182、P1642,這裡以P1642為例(二分+最短路)
#include<bits/stdc++.h> using namespace std; int n,m,b; int fee[10005],cfee[10005]; struct edge { int next,cost; }; vector<edge> edges[10005]; bool mark[10005]; struct node { int index; long long dis; bool operator <(const node &x) const { return x.dis<dis; } }; long long dis[10005]; priority_queue<node> q; bool check(int x) { if(fee[1]>x||fee[n]>x){ return false; } while(!q.empty()){ q.pop(); } for(int i=1;i<=n;i++){ if(fee[i]>x){ mark[i]=true; } else{ mark[i]=false; } dis[i]=0x7fffffffffffffff; } dis[1]=0; q.push((node){1,0}); while(!q.empty()){ node now=q.top(); q.pop();if(mark[now.index]){ continue; } mark[now.index]=true; for(int i=0;i<edges[now.index].size();i++){ int next=edges[now.index][i].next; if(mark[next]){ continue; } int cost=edges[now.index][i].cost; if(dis[next]>now.dis+cost){ dis[next]=now.dis+cost; q.push((node){next,dis[next]}); } } } if(dis[n]<=b){ return true; } else{ return false; } } int main() { scanf("%d%d%d",&n,&m,&b); for(int i=1;i<=n;i++){ scanf("%d",&fee[i]); cfee[i]=fee[i]; edges[i].clear(); } while(m--){ int a,b,c; scanf("%d%d%d",&a,&b,&c); if(a==b){ continue; } edge temp; temp.next=b; temp.cost=c; edges[a].push_back(temp); temp.next=a; edges[b].push_back(temp); } sort(cfee+1,cfee+n+1); if(!check(cfee[n])){ printf("AFK\n"); return 0; } int l=1,r=n,ans=cfee[n]; while(l<=r){ int mid=(l+r)/2; if(check(cfee[mid])){ r=mid-1; ans=cfee[mid]; } else{ l=mid+1; } } printf("%d\n",ans); return 0; }
這型別題應該立馬想到二分。難點主要在於怎麼構建出二分數列以及怎麼判斷。
對於浮點數的二分查詢,需要注意精度的問題。通常兩種方法,第一種是為迴圈設定結束的最低精度,第二種是設定最大迴圈數(後者比較保險)。POJ1064,很多細節,需要時常看一下
//POJ1064 #include<stdio.h> #include<algorithm> #include<cmath> using namespace std; #define eps 1e-10 int n,m; double len[10005]; bool judge(double mid){ int ans=0; for(int i=1;i<=n;i++){ ans+=(int)(len[i]/mid); } return ans>=m; } int main(){ scanf("%d%d",&n,&m); double l=0,r=0.0,ans; bool flag=false; for(int i=1;i<=n;i++){ scanf("%lf",&len[i]); r=max(r,len[i]); } for(int i=1;i<=100;i++){ double mid=(l+r)/2; if(judge(mid)){ l=mid; flag=true; ans=mid; } else{ r=mid; } } if(flag) printf("%.2f\n",floor(ans*100)/100); else printf("0.00\n"); return 0; }
除了上面提到的最大化最小值問題,類似的還有最大化平均值。【有n個物品的重量和價值分別為wi和vi,從中選出k個物品使得單位重量的價值最大】不是簡單的貪心,在這裡是要求總價值/總重量。這種情況可以二分,判斷當前的單位重量價值有沒有可能用前k個物品來實現。POJ3111
#include<stdio.h> #include<algorithm> using namespace std; int n,k,ans[100005]; struct item{ int loc; double value,weight,tmp; bool operator <(const item &A) const{ return tmp>A.tmp; } }items[100005]; bool judge(double x){ for(int i=1;i<=n;i++){ items[i].tmp=items[i].value-x*items[i].weight; } sort(items+1,items+n+1); double sum=0; for(int i=1;i<=k;i++){ sum+=items[i].tmp; ans[i]=items[i].loc; } return sum>=0; } int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++){ scanf("%lf%lf",&items[i].value,&items[i].weight); items[i].loc=i; } double l=0,r=10000005; for(int i=1;i<=100;i++){ double mid=(l+r)/2; if(judge(mid)) l=mid; else r=mid; } for(int i=1;i<=k;i++){ if(i!=1) printf(" "); printf("%d",ans[i]); } return 0; }
推薦題目:求第m個大的數,POJ3685。比較好想,程式碼不貼了
POJ2010,因為最後結果只與中位數成績有關與其他成績無關,那麼對每個成績進行記錄比他小的成績裡最小的(n-1)/2個f的和,以及比他大的成績裡最小的(n-1)/2個f的和。最後二分就行
#include<stdio.h> #include<algorithm> #include<queue> using namespace std; int n,c,f,h,left[100005],right[100005],ans; struct cow{ int s,f; bool operator < (const cow &x) const{ return s<x.s; } }cows[100005]; priority_queue<int> que; int main(){ scanf("%d%d%d",&n,&c,&f); for(int i=1;i<=c;i++){ scanf("%d%d",&cows[i].s,&cows[i].f); } sort(cows+1,cows+c+1); h=(n-1)/2; int sum=0; while(!que.empty()) que.pop(); for(int i=1;i<=c;i++){ left[i]=sum; if(i<=h){ sum+=cows[i].f; que.push(cows[i].f); } else{ if(cows[i].f<que.top()){ sum-=que.top(); que.pop(); que.push(cows[i].f); sum+=cows[i].f; } } } sum=0; while(!que.empty()) que.pop(); for(int i=c;i>=1;i--){ right[i]=sum; if(c-i+1<=h){ sum+=cows[i].f; que.push(cows[i].f); } else{ if(cows[i].f<que.top()){ sum-=que.top(); que.pop(); que.push(cows[i].f); sum+=cows[i].f; } } } bool flag=false; for(int i=c-h;i>=h+1;i--){ if(left[i]+cows[i].f+right[i]<=f){ flag=true; printf("%d\n",cows[i].s); return 0; } } if(!flag) printf("-1\n"); }