//联赛之前写博攒人品//
此博文,没有格式。
1.关于二分答案
如果reader没有学过二分,那么我建议您把这个网站关掉。不是我有偏见或者什么,看这篇文章对不了解二分的人来说没有好处。
对于一些问题,它的解满足单调性,即如果x满足条件,则对于任意的 i ( 1<=i<=x) 或 (x <=i <=n) (假设1和n是答案的上下界)都会满足条件。一般遇上这种问题,我们就可以用二分答案来加快解决。这种问题常常有关键语句:使最大......最小。
对于上面的问题,在没学二分答案的时候,我们是这么写的:(假设答案是上界)
for(int i=1;i<=n;++i)
if(!check(i))
{
Ans=i-1;
break;
}
那么我们该如何尽可能的少做这些无用功呢?
我们来设想一下。同样假设答案是上界,如果我们check了10000,发现它是满足解的,那么答案肯定不小于10000。如果我们又check了20000,发现它是满足解的,那么10000~20000内的数我们都不用枚举。又或者20000是不满足解的,那么答案就在10000~20000的左闭右开区间内。这个时候我们如果”恰当地“check 15000,答案的范围会进一步缩小。
看到这里我们大概都会想到二分了。一步一步地缩小答案范围最终出解。
身边的巴拉拉2016对我说:二分答案的板子大家都会啊。
int L=1,R=n;
while(L<=R)
{
int mid=(L+R)>>1;
if(check(mid))L=mid+1;
else R=mid-1;
}
printf("%d",L);
下面分享一些题目,都是NOIP里面的水题,都是可以一遍AC的。
1.NOIP2015 河中跳房子
最大距离最小,一看就知道是二分答案。
那么该如何写check呢?
我们可以check当最大距离为mid时,所需要搬走的石头的个数。方法就是一个小小的贪心——能不拿走就不拿走,能少拿走就少拿走,然后一次check在O(n)的时间内跑过。总时间复杂度是O(nlogn)。
#include我们check的返回值就是最少需要搬走的石头个数,可以证明Ans就是最优值。#include #include #include int L,M,N,pla[50100]; int gi() { int x=0;char ch=getchar(); while(ch>'9' || ch<'0')ch=getchar(); while(ch>'/' && ch<':')x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x; } int check(int x) { int Ans=0,sta=0; for(int i=1;i<=N;++i) { while(pla[i]-sta >1,S=check(mid); if(S>M)r=mid-1; else l=mid+1; } printf("%d",r); }
2.NOIP2012 借教室
因为答案具有单调性——答案之前的肯定都能借到。所以我们来二分答案。
那么该如何写check函数呢?
对于这道题,我们需要干的事有:区间增加和单点求值。可能大犇都想到了线段树,但这种复杂度贼高的算法最高只能过90分。我们没必要用高级算法,可以用一种小技巧处理——差分+前缀和。
所谓差分,就是把区间的操作转移到对区间端点的操作。比如我们对一个都是0的数组,要在5~13这段区间加上3,只要在5这个点上加上3,在14这个点上减掉3,这样在算前缀和的时候,0~4都是0,5~13都是3,14及以后又都成了0。这种操作修改是O(1),查询是O(n),但你可以一遍查询求出所有的点是否合法。所以查询的总复杂度就是O(n+m),题目的复杂度就是O((n+m)logm)。
#include我们check函数返回的是当前订单能否满足。#include #include using namespace std; int n,m,num[1010000],d[1010000],sta[1010000],end[1010000],Q[1010000],L,R; int gi() { int x=0;char ch=getchar(); while(ch>'9' || ch<'0')ch=getchar(); while(ch>'/' && ch<':')x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x; } bool check(int x) { int total=0; for(int i=1;i<=n;++i)Q[i]=0; for(int i=1;i<=x;++i)Q[sta[i]]+=d[i],Q[end[i]+1]-=d[i]; for(int i=1;i<=n;++i) { total+=Q[i]; if(total>num[i])return false; } return true; } int main() { n=gi();m=gi(); for(int i=1;i<=n;++i)num[i]=gi(); for(int i=1;i<=m;++i)d[i]=gi(),sta[i]=gi(),end[i]=gi(); L=1;R=m; while(L<=R) { int mid=(L+R)>>1; if(check(mid))L=mid+1; else R=mid-1; } if(L>m)printf("0"); else printf("-1\n%d",L); return 0; }
3.NOIP2011 聪明的质检员
一看就知道是二分答案:答案具有单调性,图像趋势类似于一个二次函数曲线,我们只要求出这个函数的顶点最近的整数就好了。二分答案,记录当前答案,比较一下就好了。
这里要解释一下那个式子的意思:L到R内所有满足Wj>W的j的个数乘以它们的体积和。这个可以用前缀和维护。开两个前缀和维护一下个数和体积和就好了。
#include
#include
#include
#include
#define LL long long int
LL n,m,s,l[201000],r[201000],V[201000],W[201000],Qnum[200100],QY[200100],maxR;
LL gi()
{
LL x=0;char ch=getchar();
while(ch>'9' || ch<'0')ch=getchar();
while(ch>'/' && ch<':')x=x*10+ch-48,ch=getchar();
return x;
}
LL max(LL x,LL y){return x>y?x:y;}
LL min(LL x,LL y){return x=x);
QY[i]=QY[i-1]+V[i]*(W[i]>=x);
}
for(int i=1;i<=m;++i){Ans+=(Qnum[r[i]]-Qnum[l[i]-1])*(QY[r[i]]-QY[l[i]-1]);}
return Ans-s;
}
int main()
{
n=gi();m=gi();s=gi();
for(int i=1;i<=n;++i)W[i]=gi(),V[i]=gi(),maxR=max(maxR,W[i]);
for(int i=1;i<=m;++i)l[i]=gi(),r[i]=gi();
{
LL L=1,R=maxR,K=10000000000000;
while(L<=R)
{
LL mid=(L+R)>>1,S=check(mid);
if(S<0)R=mid-1,K=min(-S,K);
else L=mid+1,K=min(S,K);
}
printf("%lld\n",K);
}
return 0;
}
check函数返回的是当前答案下的W的原值(不能带abs())
如果小于0,证明这个点在原点右边,要往左边挪。否则往右边挪。
不想写了,发了算了,看同学跳舞去了。
嘻嘻嘻嘻。
附:如果借教室没看懂,我旁边的巴拉拉2016也写了一个博。但以他高产低质的特性,我不指望你们能看懂。
如果质检员没看懂,我旁边的巴拉拉2016还写了一个博。但他一直很不走心,我只是骗你们去加访问量。