二分总结

二分总结


二分时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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值