二分总结
二分时eps一般比题目要求的精度多2位,若题目没有明确要求,则可以固定二分次数。
一.整数域二分
POJ 1759 Garland
N个灯泡离地H_i,满足H1 = A ,Hi = (Hi-1 + Hi+1)/2 – 1,Hi>=0,HN = B ,求最小B。
一般是二分答案,而此题如果二分答案不好判断。所以去二分H2,每次判断Hi是否大于0,记录B值。
POJ3579 Median
题意:给出N个数,每两个数的差值构成了一个序列。求这个序列的中位数。如果这个序列有偶数个元素,就取中间偏小的作为中位数。N<=100000
我们发现大于中位数的数的个数是固定的。所以可以二分中位数。
对于区间l点的贡献,就是以l为左端点,从某个r开始一直到n的区间。在l的增加中,r是单调不减的。
所以可以用尺取法。每次验证时判断大于中位数的数有无所有数的一半即可。
bool Check(ll x){
ll j=1,ret=0;
for(ll i=1;i<=n;++i){
while(a[j]-a[i]<x&&j<=n) ++j;
ret+=(n-j+1);
}
if(ret>=m) return true;
return false;
}
POJ 3484 Showstopper
题目大意:每次给出三个数x,y,z,用这三个数构成一个等差数列,x为首项,y是末项,z是公差.总共给出n组x,y,z( n待定),求这n组数列中出现次数为奇数的那个数以及该数出现的次数(保证最多有一个数出现的次数为奇数)
读入十分毒瘤
sscanf与scanf的区别是前者针对特定的字符串(已知的),后者是从stdin从读入的。
题目解法非常巧妙,二分一个数,如果这个数前有出现奇数次的数,那么此时的前缀和是奇数,否则为偶数。直到二分找到这个数。
前缀和是这样的:偶偶偶偶偶偶偶偶偶偶奇奇奇奇奇奇
bool read(){
x.clear();y.clear();z.clear();
bool flg=0;
while(gets(s)){
if(!strlen(s)){
if(flg) break;
else continue;
}
ll xx,yy,zz;
sscanf(s,"%lld%lld%lld",&xx,&yy,&zz);
flg=1;
x.push_back(xx);
y.push_back(yy);
z.push_back(zz);
}
n=x.size();
return flg;
}
shop
【题目描述】
有n种物品,第i种物品的价格为vi,每天最多购买xi个。
有m天,第i天你有wi的钱,你会不停购买能买得起的最贵的物品。你需要求出你每天会购买多少个物品。
【输入数据】
第一行两个整数n,m。接下来n行每行两个整数vi,xi。接下来m行每行一个整数wi。
【输出数据】
m行每行一个整数,第i行表示第i天购买的物品数量。
【样例输入】
3 3
1 1
2 2
3 3
5
10
15
【样例输出】
2
4
6
【数据范围】
对于20%的数据,n,m<=1000。
对于另外40%的数据,xi=1。
对于100%的数据,n,m<=100000,1<=vi<=109,1<=xi<=10000,0<=wi<=1018。
跟0907中的“膜”及其相似,每次购买了一种物品后w的值至少减半。所以就二分咯。
首先从大到小排序,二分出当前可买的最大值。
然后二分当前区间,如果一个区间也买不到,就二分当前物品买多少个。
其实首先想到的是暴力买,然后就想到了树上一直往上的找一般用倍增优化,这里不好倍增,但是可以二分。
const ll maxn = 100000 + 10;
ll n,m,w,l,r,mid,ans,la,cnt,pos,tmp;
ll sum[maxn],num[maxn];
struct node{
ll v,x;
bool operator < (const node &b) const{
if(v != b.v) return v > b.v;
return x > b.x;
}
}a[maxn];
int main(){
n = read();m = read();
for(ll i = 1; i <= n; ++i){
a[i].v = read();
a[i].x = read();
}
sort(a + 1,a + n + 1);
for(ll i = 1; i <= n; ++i){
sum[i] = sum[i - 1] + a[i].v * a[i].x;
num[i] = num[i - 1] + a[i].x;
}
while(m--){
w = read();
tmp = 1; ans = 0;
while(1){
l = tmp;r = n;tmp = -1;
while(l <= r){
mid = (l + r) >> 1;
if(a[mid].v <= w){
r = mid - 1;
tmp = mid;
}
else l = mid + 1;
}
if(tmp == -1) break;
l = tmp,r = n;pos = -1;
while(l <= r){
mid = (l + r) >> 1;
if(sum[mid] - sum[tmp - 1] <= w) {
l = mid + 1;
pos = mid;
}
else r = mid - 1;
}
if(pos != -1){
ans += num[pos] - num[tmp - 1];
w -= sum[pos] - sum[tmp - 1];
tmp = pos + 1;
}
else{
l = 1;r = a[tmp].x;cnt = -1;
while(l <= r){
mid = (l + r) >> 1;
if(a[tmp].v * mid <= w){
l = mid + 1;
cnt = mid;
}
else r = mid - 1;
}
if(cnt != -1){
ans += cnt;
w -= cnt * a[tmp].v;
tmp++;
}
}
}
write(ans);putchar('\n');
}
return 0;
}
二.01分数规划
∑ i = 1 n a [ i ] ∗ x [ i ] ∑ i = 1 n b [ i ] ∗ x [ i ] \sum_{i=1}^{n}a[i]*x[i]\over \sum_{i=1}^{n}b[i]*x[i] ∑i=1nb[i]∗x[i]∑i=1na[i]∗x[i]的最值。
两种方法:
1.二分
2.Dinkelbach迭代法
二分:将上面的不等式转化为:
二分上式的值T。移项。 = x [ i ] ∗ ( a [ i ] − b [ i ] ∗ T ) =x[i]*(a[i]-b[i]*T) =x[i]∗(a[i]−b[i]∗T);
可以转化为判定问题。
POJ 3111 K Best
有n个物品的重量和价值分别是wi和vi。从中选出k个物品使 ∑ i = 1 n v [ i ] ∑ i = 1 n w [ i ] \sum_{i=1}^{n}v[i]\over \sum_{i=1}^{n}w[i] ∑i=1nw[i]∑i=1nv[i]最大。
bool Check(double x){
double ret=0;
for(int i=1;i<=n;++i)
a[i]=v[i]-w[i]*x;
sort(a+1,a+n+1);
for(int i=1;i<=k;++i)
ret+=a[n-i+1];
return ret>=0;
}
三.实数域二分
Best Cow Fences POJ - 2018
这是道实数域的二分题。
题目大意:给定一个正整数列A,求一段长度大于等于F的区间的平均值最大为多少。
二分出答案t,让每个数减去t,对于当前的i去寻找sum[0…i-f]中的最小值。如果两值相减,答案大于等于0,则存在。
注意实数域上二分的写法。
const double eps = 1e-5;
int n,f;
bool Check(double t){
double mn = inf,ans = -inf;
for(int i = 1; i <= n; ++i)
sum[i] -= t * i;
for(int i = f; i <= n; ++i){
mn = min(mn,sum[i - f]);
ans = max(ans,sum[i] - mn);
}
for(int j = 1; j <= n; ++j) sum[j] += t * j;
if(ans >= 0) return true;
return false;
}
double mn,mx;
int main(){
mn = inf;mx = -inf;
scanf("%d%d",&n,&f);
for(int i = 1; i <= n; ++i){
scanf("%lf",&a[i]);
mn = min(a[i],mn);
mx = max(a[i],mx);
sum[i] = sum[i - 1] + a[i];
}
double l = mn,r = mx,mid,ans;
while(r - l > eps){//!!
mid = (l + r) / 2;//!!
if(Check(mid)) l = mid;//!!
else r = mid;//!!
}
printf("%d\n",(int)(r * 1000));//!!
return 0;
}