序言
對於單調性或二段性的對象一般會考慮二分答案。
把該問題轉化為給定一個值mid,判定是否可行,進而縮小范圍。
模型
此類問題對於答案從屬於右邊的,則選用“r=mid”的模板;對於答案從屬於左邊的,則選用“l=mid”的模板。
這類問題其實可以根據絕對值的性質,將其划分成兩個子問題。
大於等於該值的答案中最小的&小於等於該值的答案中最大的
那么便轉化成上一類問題了。
根據單調性二分即可。
典題詳析
題面分析:
題目要求“剩余的電纜中,最貴的那條的最少花費”,這就是一個典型的“最大值最小”的模型。
算法思路:
二分路徑上第k+1長的邊的權值,使其最小化。
對於路徑上邊權大於該值的邊,是必須用一次免費機會的。那么把其值記為1;
否則是不需用掉免費機會的,則把其值記為0;
然后跑一遍01最短路即可。
代碼如下:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <queue> using namespace std; const int N = 1e3+10,M = 2e4+10; struct Edge{ int to,next,w; }edge[M]; int idx; int h[N]; int vis[N],dis[N]; int n,m,k; void add(int u,int v,int w) { edge[++idx].w=w; edge[idx].to=v; edge[idx].next=h[u]; h[u]=idx; } bool check(int mid) //SLF優化的SPFA最短路 { deque<int> q; memset(dis,0x3f,sizeof dis); memset(vis,0,sizeof vis); dis[1]=0; vis[1]=1; q.push_back(1); while(!q.empty()) { int now=q.front(); q.pop_front(); vis[now]=0; for(int i=h[now];~i;i=edge[i].next) { int to=edge[i].to,w; if(edge[i].w<=mid)w=0;else w=1; if(dis[to]>dis[now]+w) { dis[to]=dis[now]+w; if(!vis[to]) { if(!q.empty()&&dis[to]<dis[q.front()]) q.push_front(to); else q.push_back(to); vis[to]=1; } } } } if(dis[n]<=k)return true; else return false; } int main() { memset(h,-1,sizeof h); scanf("%d%d%d",&n,&m,&k); int maxx=-2e9; for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); maxx=max(maxx,w); } int l=0,r=maxx; while(l<r) { int mid=l+r>>1; if(check(mid))r=mid; else l=mid+1; } if(!check(l))puts("-1"); else printf("%d\n",l); return 0; }
2. NOIP提高組2011 聰明的質監員 洛谷P1314 / Acwing 499
題面分析:
仔細思考,不難發現如下性質:
W越大,則重量大於等於W的礦石就會越少,那么Y就越小;
W越小,則重量大於等於W的礦石就會越多,那么Y就越大。
也就是說,Y與W呈負相關,Y隨W單調變化。
算法思路:
我們二分W的值,然后進行判定。
找出滿足條件“使得Y小於等於S”的W中的最小值,即滿足條件的Y的最大值。
那么最后再算一遍W-1的Y值,即是Y大於等於S的Y的最小值。
對於滿足條件的礦石個數與價值的區間求和,我們很容易想到用前綴和來處理。
代碼如下:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <cmath> using namespace std; const int N =2e5+10,M=2e5+10;; long long s; int n,m; int sum_n[N],sum_v[N]; int w[N],v[N],l[M],r[M]; long long valid(int mid) { memset(sum_n,0,sizeof sum_n); memset(sum_v,0,sizeof sum_v); for(int i=1;i<=n;i++) { if(w[i]>=mid) { sum_n[i]=sum_n[i-1]+1; sum_v[i]=sum_v[i-1]+v[i]; } else { sum_n[i]=sum_n[i-1]; sum_v[i]=sum_v[i-1]; } } long long ans=0; for(int i=1;i<=m;i++) { ans+=(long long)(sum_n[r[i]]-sum_n[l[i]-1])*(sum_v[r[i]]-sum_v[l[i]-1]); } return ans; } int main() { scanf("%d%d%lld",&n,&m,&s); int L=2e9,R=-2e9; for(int i=1;i<=n;i++) { scanf("%d%d",&w[i],&v[i]); L=min(L,w[i]); R=max(R,w[i]); } for(int i=1;i<=m;i++)scanf("%d%d",&l[i],&r[i]); long long ans=2e18; while(L<R) { int mid=L+R>>1; long long res=valid(mid); if(res<=s)R=mid; else L=mid+1; } ans=min(abs(valid(L)-s),abs(valid(L-1)-s)); printf("%lld\n",ans); return 0; }
3. NOIP提高組2012 借教室 洛谷P1083 / Acwing 503
題面分析:
當前一份的訂單已經無法滿足的時候,那么后續的訂單也必然無法滿足。
於是我們發現了能否滿足的訂單數的單調性。
算法思路:
二分訂單數,即題目要求的第一份無法滿足的訂單編號。
那么對於每次的二分值,進行判定:
到當前這份訂單為止,對於每一天要租借的教室的數量進行累加,看原有的教室數量是否足夠。
每次判定都需要對區間進行加減操作,那么我們自然想到用差分進行處理。
代碼如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 1e6+10; int n,m; int s[N],t[N],d[N],f[N],c[N]; bool check(int mid) { memcpy(c,f,sizeof c); for(int i=1;i<=mid;i++) { c[s[i]]-=d[i]; c[t[i]+1]+=d[i]; } int sum=0; for(int i=1;i<=n;i++) { sum+=c[i]; if(sum<0)return true; } return false; } int main() { scanf("%d%d",&n,&m); int la=0; for(int i=1;i<=n;i++) { int a;scanf("%d",&a); f[i]=a-la;la=a; } for(int i=1;i<=m;i++)scanf("%d%d%d",&d[i],&s[i],&t[i]); int l=1,r=n+1; while(l<r) { int mid=l+r>>1; if(check(mid))r=mid; else l=mid+1; } if(l==n+1)puts("0"); else printf("-1\n%d\n",l); }