文章目录
二分搜索
最大化最值
River Hopscotch(POJ 3258)
题目链接:River Hopscotch
- 题目大意:一条河长度为 L,河的起点(Start)和终点(End)分别有2块石头,S到E的距离就是L。
河中有n块石头,每块石头到S都有唯一的距离。
问现在要移除m块石头(S和E除外),每次移除的是与当前最短距离相关联的石头,要求移除m块石头后,使得那时的最短距离尽可能大,输出那个最短距离。 - 思路:二分搜索题目最重要的是要找到判断二分的条件。本题目的二分条件不是很好找,但是我们可以转换一下思路。我们可以找能够满足某个最短距离x的区间个数,因为要移走m块石头,所以我们需要找到满足最短距离x的区间个数为n-m-1个。即这些区间的的长度均不小于最短距离x。这需要从头遍历所以石头,然后找出所有大于x的区间(且这些区间不能交叉)。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAX = 50005;
const LL INF = 1000000005;
LL L, N, M, lb, ub, mid;
LL a[MAX];
bool solve(LL x)
{
int st = 0, ed = 1, sum = 0;
for(; ed<N; ed++)
{
if(a[ed]-a[st]>=x)//寻找满足条件的区间
{
st = ed;//开始下一个区间
sum++;
}
}
if(sum>=N-M-1) return 1;
else return 0;
}
int main()
{
scanf("%lld%lld%lld", &L, &N, &M);
a[0] = 0;
for(int i=1; i<=N; i++)
scanf("%lld", &a[i]);
a[N+1] = L;
N = N+2;//注意加上首尾的石头
sort(a, a+N);
lb = 0, ub = INF;
while(ub-lb>1)
{
mid = (ub+lb)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
printf("%lld\n", lb);
return 0;
}
Monthly Expense(POJ 3273)
题目链接:Monthly Expense
- 题目大意:将N个账款分割成M个连续财务期,使得每个分期账款和的最大值最小。
- 思路:该二分搜索的二分条件是在确定的每一个分期账款值为x的情况下,计算其能够分m期,且要保证m<M。满足条件时表示x足够大,可以小一点,不满足条件时增加x。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX = 100001;
int a[MAX];
int N, M, lb, ub, mid;
bool solve(int x)
{
int sum = 0, cnt = 1;
for(int i=0; i<N; i++)
{
if(a[i]>x) return 1;//证明x太小
if(sum+a[i]<=x)
{
sum += a[i];
}
else
{
cnt++;
sum = 0;
i--;
}
}
if(cnt<=M) return 0;//x足够大
else return 1;
}
int main()
{
lb = 0, ub = 1;
scanf("%d%d", &N, &M);
for(int i=0; i<N; i++)
{
scanf("%d", &a[i]);
ub += a[i];
}
while(ub>lb+1)
{
mid = (ub+lb)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
printf("%d\n", ub);
return 0;
}
Drying(POJ 3104)
题目链接:Drying
- 题目大意:Jane希望计算出所有的衣服都烘干的最短时间,每件衣服一开始都有ai的水分,自然状态下每件衣服在单位时间内都会减少一份水,并且jane有烘干机,烘干机每次只能烘干一件衣服,使用机器烘衣服一个单位时间可以让衣服减少K份水(但是烘干时就不会自然蒸发那1份的水分),现在需要让所有衣服的水分含量都降低至0,至少需要多少时间。
- 思路:二分条件的选择。假设得到了时间x,则需要判断x是否满足条件。则现将所有的ai减去x,表示如果均不用烘干机最后剩的水分。此时可以将ai中小于等于0的排除,因为这些不需要烘干机也能在时间内自动干。然后剩下的就可以放入烘干机中了,只不过此时k变成了k-1。计算使用烘干机的时间cnt,如果cnt<=x,则表明符合条件(x还可以降低),否则不符合条件(x需要增大)。
注意:k可能为1,需要单独讨论
。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int MAX = 1e5+50;
string s;
LL n, a[MAX], k, lb, ub, mid;
bool solve(LL x)
{
LL cnt = 0, b;
for(LL i=0; i<n; i++)
{
b = a[i];
b -= x;
if(b<=0) continue;
cnt += b/(k-1);//有可能k为1,需要在前面将这种情况分出讨论
if(b%(k-1)) cnt++;
}
if(cnt<=x) return 0;
else return 1;
}
int main()
{
scanf("%lld", &n);
lb = 0, ub = 1;
LL M = 0;
for(LL i=0; i<n; i++)
{
scanf("%lld", &a[i]);
ub += a[i];
if(M<a[i]) M = a[i];//选出最大的一个
}
scanf("%lld", &k);
if(k==1)
{
printf("%lld\n", M);
return 0;
}
while(ub>lb+1)
{
mid = (ub+lb)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
printf("%lld\n", ub);
return 0;
}
Cow Acrobats(POj 3045)
题目链接:Cow Acrobats
参考博文:POJ Cow Acrobats
- 题目大意:将N头牛叠成犇,每头牛的力气是S_i,体重是W_i,倒下的风险是身上的牛的体重和减去S_i,求最稳定犇的最大risk。
- 思路:贪心算法。力气越大,体重越重的在下面,按这样排序就可以得到最优的情况,然后遍历计算这种情况下的最大风险,输出即可。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAX = 50005;
const LL INF = 1e9+5;
struct Node
{
LL w, s;
bool operator < (const Node &A) const
{
return w+s<A.w+A.s;
}
};
Node a[MAX];
int N;
void solve()
{
LL ans = -INF, sum = 0;
for(int i=0; i<N; i++)
{
ans = max(ans, sum-a[i].s);
sum += a[i].w;
}
printf("%lld\n", ans);
}
int main()
{
scanf("%d", &N);
for(int i=0; i<N; i++)
{
scanf("%lld%lld", &a[i].w, &a[i].s);
}
sort(a, a+N);
solve();
return 0;
}
最大化平均值
Dropping tests(POJ 2976)
题目链接:Dropping tests
参考博文:Dropping tests
- 题目大意:
- 思路:0-1分数划分问题。可以使用二分搜索解决,但是重点在于二分条件。具体参考参考博文。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX = 1005;
struct Node
{
double a, b;
};
Node c[MAX];
double t[MAX];
int k, n;
double lb, ub, mid;
bool solve(double x)
{
for(int i=0; i<n; i++)
{
t[i] = 100*c[i].a-x*c[i].b;
}
sort(t, t+n);
double sum = 0;
for(int i=k; i<n; i++)
sum += t[i];
if(sum>=0) return 1;//x可以更大
else return 0;
}
int main()
{
while(scanf("%d%d", &n, &k)!=EOF)
{
if(n==0 && k==0) break;
lb = 0, ub = 101;
for(int i=0; i<n; i++)
{
scanf("%lf", &c[i].a);
}
for(int i=0; i<n; i++)
{
scanf("%lf", &c[i].b);
}
for(int i=0; i<100; i++)
{
mid = (ub+lb)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
printf("%.0f\n", lb);
}
return 0;
}
K Best(POJ 3111)
题目链接:K Best
- 题目大意:有N颗珠宝,每颗珠宝的价值为vi,重量为wi。 女主不得已要卖掉部分珠宝,她想留下k颗珠宝,并要求(v1+v2+…vk) / (w1+w2+…wk)的值最大,输出女主留下的珠宝的编号。(可不按输入的顺序输出)。
- 思路:思路与Dropping tests相似,均是分数划分。只不过该了一下二分条件,并保存了下标用于输出。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX = 100005;
const int INF = 1e7+1;
struct Node
{
int v, w;
}a[MAX];
struct Nod
{
double x;//注意数据类型
int id;
bool operator < (const Nod &A) const
{
if(x==A.x)
return id<A.id;
else
return x>A.x;
}
}t[MAX];
int k, n;
double lb, ub, mid;//注意数据类型
bool solve(double x)
{
for(int i=0; i<n; i++)
{
t[i].x = a[i].v-x*a[i].w;
t[i].id = i+1;
}
sort(t, t+n);
double sum = 0;
for(int i=0; i<k; i++)
sum += t[i].x;
if(sum>=0) return 1;
else return 0;
}
int main()
{
scanf("%d%d", &n, &k);
for(int i=0; i<n; i++)
scanf("%d%d", &a[i].v, &a[i].w);
lb = 0, ub = INF;
while(ub>lb+1e-6)//注意精度
{
mid = (ub+lb)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
for(int i=0; i<k; i++)
{
if(i!=0) printf(" ");
printf("%d", t[i].id);
}
printf("\n");
return 0;
}
查找第k大的值
Median(POJ 3579)
题目链接:Median
参考博文:POJ 3579 Median 查找中间值 二分
- 题目大意:给出n(3<=n<=100000)个数,f(i,j)=|a[i]-a[j]| (1<=i<j<=n)。求所有的f(i,j)里面中位数的值。
- 思路:给出中位数mid,需要判断该中位数是否满足条件。排序所用元素,遍历每一个元素,然后二分查找得到大于该元素ai并小于ai+mid的元素个数,然后累加得到在mid左边的元素的个数cnt,并判断cnt与m/2(m是差值个数)的关系。大于等于表示mid太大,小于表示mid太小。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAX = 100005;
LL x[MAX], N, M;
LL lb, ub, mid;
bool solve(LL mid)
{
LL cnt = 0;
for(int i=0; i<N; i++)
{
cnt += upper_bound(x+i, x+N, x[i]+mid)-x-i-1;
}
if(cnt>=M) return 0;//mid可以减少
else return 1;//mid可以增加
}
int main()
{
while(scanf("%lld", &N)!=EOF)
{
M = (N-1)*N/2;
if(M%2) M = M/2+1;
else M = M/2;
for(int i=0; i<N; i++)
scanf("%lld", &x[i]);
sort(x, x+N);
lb = 0, ub = x[N-1]-x[0]+1;
while(ub>lb+1)
{
mid = (lb+ub)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
printf("%lld\n", ub);
}
return 0;
}
Matrix(POJ 3685)
题目链接:Matrix
参考博文:POJ - 3685 Matrix 二分
- 题目大意:有一个N * N的矩阵,其中Aij = i * i + i * 100000 - 100000 * j + j * j + i * j,问这个矩阵中,第M小的数是多少。
- 思路:需要从题目中得到当j不变时,随着i的增大,Aij也增大。所以每一列均是已经排好序的数组。所以和Median(POJ 3579)相似,累加每一列小于给定值mid的个数,得到cnt,然后判断cnt与M的关系,小于表示mid太小,否则表示mid太大。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL INF = 1<<29;
int T;
LL lb, ub, mid, N, M;
LL caculate(LL m, LL n)
{
return m*m+100000*m+n*n-100000*n+m*n;
}
bool solve(LL x)
{
LL cnt = 0;
for(int j=1; j<=N; j++)
{
LL l = 1, r = N, mid;
//寻找合适的元素下标
while(r>=l)
{
mid = (r+l)/2;
if(caculate(mid, j)<=x)
l = mid+1;
else
r = mid-1;
}
cnt += l-1;
}
if(cnt>=M) return 0;
else return 1;
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%lld%lld", &N, &M);
//注意初始数据
lb = -(LL)(N*N*3+100000*N), ub = (LL)(N*N*3+100000*N);
while(ub>lb+1)
{
mid = (lb+ub)/2;
if(solve(mid)) lb = mid;
else ub = mid;
}
printf("%lld\n", ub);
}
return 0;
}
最小化第k大的值
Telephone Lines(POJ 3662)
题目链接:Telephone Lines
参考博文:POJ 3662 Telephone Lines 题解 《挑战程序设计竞赛》
- 题目大意:N个电线杆P条线可选,K条线内免费,否则花费免费额度外最长的那一根。求最小花费。
- 思路:最短路+二分。因为在k条线内免费,所以可以设免费额度外最长的一根电线长度为mid,则通过最短路遍历得到长度不小于mid的边的个数,如果个数大于K,则表示mid比较小,否则mid足够大,最终输出的是lb。其中d[]表示在最短路中边的长度高于mid的边的个数。具体解释参考博文。
代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX = 1005;
const int INF = 1000001;
int N, P, K, A, B, L, lb, ub, mid;
int d[MAX], vis[MAX];
struct edge
{
int v, d;
edge(int v = 0, int d = 0): v(v), d(d){}
bool operator < (const edge &A) const
{
return d>A.d;
}
};
vector<edge> G[MAX];
int dijstra(int x)
{
memset(vis, 0, sizeof(vis));//注意,使用vis可以减枝,否则超时
fill(d, d+N+1, INF);
priority_queue<edge> q;
d[1] = 0;
q.push(edge(1, d[1]));
while(!q.empty())
{
edge u = q.top(); q.pop();
int uv = u.v, ud = u.d;
vis[uv] = 1;
if(d[uv]<ud) continue;//减枝
for(int i=0; i<G[uv].size(); i++)
{
int t = G[uv][i].v, c = G[uv][i].d, dis;
if(c>=x) dis = d[uv]+1;//一定是>=
else dis = d[uv];
if(!vis[t] && dis<d[t])
{
d[t] = dis;
q.push(edge(t, d[t]));
}
}
}
return d[N];
}
int main()
{
scanf("%d%d%d", &N, &P, &K);
for(int i=0; i<P; i++)
{
scanf("%d%d%d", &A, &B, &L);
G[A].push_back(edge(B, L));
G[B].push_back(edge(A, L));
}
lb = 0, ub = INF+2;
while(ub>lb+1)
{
mid = (ub+lb)/2;
int n = dijstra(mid);
if(n>K) lb = mid;//一定保证大于等于mid的至少有k+1个,才能代表mid可以作为结果
else ub = mid;
}
if(lb>INF)
printf("-1\n");
else
printf("%d\n", lb);
return 0;
}
Garland(POJ 1759)
题目链接:Garland
参考博文:POJ—1759(Garland,二分一个,求另一个的最优)
- 题目大意:有n个数字H,H[i]=(H[i-1]+H[i+1])/2-1,已知H[1],求最大H[n],
使得所有的H均大于0. - 思路:我们得到递推式子H[i]=2*H[i-1]+2-H[i-2],发现H[n]和H[2]成正相关。
所以我们只要二分H[2]的取值,同时计算每个H是否大于等于0即可。同时可以减枝优化一下。
代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const double INF = 1002;
double A, B, H, lb , ub, mid;
int N;
bool solve(double x)
{
double H2 = A, H1 = x;//一定使用变量存储
for(int i=3; i<=N; i++)
{
H = H1*2+2-H2;
if(H<0) return 1;//小于0,表示x不够大
if(H+2>H1) return 0;//减枝,递增且已经大于0,则不会再小于0
H2 = H1;
H1 = H;
}
return 0;
}
int main()
{
while(scanf("%d%lf", &N, &A)!=EOF)
{
lb = -1, ub = INF;
//二分求第2个变量
for(int i=0; i<100; i++)
{
mid = (lb+ub)/2.0;
if(solve(mid)) lb = mid;
else ub = mid;
}
//得到最优的第2个变量,再从头到尾算一遍
for(int i=3; i<=N; i++)
{
H = ub*2+2-A;
A = ub;
ub = H;
}
printf("%.2f\n", H);
}
return 0;
}
Showstopper(POJ 3484)
题目链接:Showstopper
参考博文:POJ-3484-Showstopper
- 题目大意:给出一组x,y,z 每组包含一个集合{x+k*z<=y,k=0,1,2,3…}, 所有集合中只有一个数出现奇数次或者全部出现偶数次,如果有数出现奇数次,输出那个数以及出现的次数,否则输出 no corruption。
- 思路:因为只可能有一个数出现奇数次,假设为x,所有数出现次数的和依次为 偶 偶 偶 奇(x) 奇 奇 奇 奇 x之前为偶数,x之后为奇数,而所有数出现的次数可以根据公式直接算出,再根据这个单调性二分出答案。本题目的重点在与输入,不同样例之间有多个空行,且不知道有多少个样例,每个样例有多少行数据。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const long long INF = 1LL<<35;
const int MAX = 5000000;
LL X, Y, Z, t, ans;
LL lb, ub , mid;
string s;
struct Node
{
LL x, y, z;
}f[MAX];
LL solve(LL x)
{
LL cnt = 0;
for(int i=0; i<t; i++)
{
if(x>=f[i].x)
cnt += min(x-f[i].x, f[i].y-f[i].x)/f[i].z+1;
}
return cnt;
}
void compute()
{
lb = 0, ub = INF, ans = 0;
while(ub>lb+1)
{
mid = (lb+ub)/2;
if(solve(mid)%2==0) lb = mid;
else ub = mid;
}
if(ub==INF)
{
printf("no corruption\n");
}
else
{
printf("%lld %lld\n", ub, solve(ub)-solve(ub-1));
}
}
int main()
{
//本题的难点在于输入。不同测试样例之间有多个空行,输入结束后需要在循环外在加一个处理
t = 0;
while(getline(cin, s))
{
if(s.size()==0)
{
if(t==0)
continue;
compute();
t = 0;
}
else
{
int a[5] = {0}, k = 0;
for(int i=0; i<s.size(); i++)
{
if(s[i]!=' ')
{
a[k] = a[k]*10+s[i]-'0';
}else
{
k++;
}
}
f[t].x = a[0], f[t].y = a[1], f[t++].z = a[2];
}
}
if(t)
compute();
return 0;
}
尺取法
Bound Found(POJ 2556)
题目链接:Bound Found
参考博文:POJ-Bound Found | 尺取法+绝对值特性
- 题目大意:给定一个数列,求某个子序列的和的绝对值最接近给定的t,输出这个序列的和的绝对值,左右端点。
- 思路:将和的绝对值转换为前缀和数组,然后将数组排序得到单调数组,然后可以使用尺取法,取的是任意两个元素之差(单调增)。
代码:
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#define LL long long
#define P pair<int,int>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
P p[maxn];
bool cmp(P a,P b)
{
return a.first<b.first;
}
int main()
{
int n,k,x,t,sum;
while(scanf("%d%d",&n,&k)&&n+k)
{
sum=0;
p[0]=make_pair(0,0);
for(int i=1; i<=n; ++i)
{
scanf("%d",&x);
sum=sum+x;
p[i]=make_pair(sum,i);
}
sort(p,p+n+1,cmp);
while(k--)
{
scanf("%d",&t);
sum=0;
int l=0,r=1,ansl,ansr,mi=INF,ans;
while(r<=n&&mi)
{
sum=p[r].first-p[l].first;
if(abs(sum-t)<=mi)
{
mi=abs(sum-t);
ans=sum;
ansl=p[l].second;
ansr=p[r].second;
}
if(sum<t) r++;
else l++;
if(l==r) r++;
}
if(ansl>ansr) swap(ansl,ansr);
printf("%d %d %d\n",ans,ansl+1,ansr);
}
}
return 0;
}