一共10道题,除了最后一题没想到怎么用二分,最后用递归写出来之外,其他题目都可以归为几类经典的二分题,这次将题目按类分出
具体按类分比较基础的讲解可以参考《挑战程序设计》二分法一章
有两题有点不太熟的放在的最后
可以跳到最后看 primary X-subfactor series *********
和Exams *******
类型一:二分答案验证是否可行
Pie (poj3122)
题意: 一共n个披萨,加上我(f+1) 个人,吃披萨~
每个人只能从一个披萨(陷阱)中取一块,已知每个披萨的半径,每个人取的披萨面积一样,求可以取到的最大面积
误差要求不超过10^-3
思路: 二分面积,判断 求每个披萨能贡献多少块,总和大于人数则可行
注意: 精度的问题啊…… WA了好多次,有 两处 细节看代码注释
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n,f;
double s[10010];
const double pi=acos(-1); //用acos(-1) 不能用3.1415926 否则精度不够WA
bool check(double mid){
int ans=0;
for(int i=n-1;i>=0;i--){
ans+=int(s[i]/mid); //少了个int也wa T T
}
return ans>=f;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&f);
f++;
memset(s,0,sizeof(s));
double l=0;
double r=0;
for(int i=0;i<n;i++){
int rr;
scanf("%d",&rr);
s[i]=pi*rr*rr;
r+=s[i];
}
while(fabs(r-l)>0.000001){ //注意精度嗯
double mid=(l+r)/2.0;
if(check(mid))
l=mid;
else
r=mid;
// printf("%lf\n",mid);
}
printf("%.4lf\n",l);
}
return 0;
}
String game 二分区间
题意: 已知字符串s和字符串t,告诉你数组 a[1],a[2],……,a[n],每个数字对应字符串中一个位置,求最多按数组的顺序删多少个s中的字符,s 仍可以生成 t
思路: 二分位置,如果可行则二分后一段,否则前一段
想清楚二分比不二分优化在哪里
我不是很理解这有什么优化的地方…… 而且这个复杂度好像也不太会算啊 ORZ
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
char str[200100];
int mark[200100];
char estr[200100];
int per[200100];
int n,n2;
int mid2;
bool check(int mid){
if(mid2<mid) //在上一个二分的基础上记录每个位置的字符有没有被拿走,节约点时间……
for(int i=mid2;i<=mid;i++){
int t=per[i]-1;
mark[t]=0;
}
else
for(int i=mid+1;i<=mid2;i++){
int t=per[i]-1;
mark[t]=-1;
}
int cnt=0;
for(int i=0;i<n;i++){
if(mark[i]){
if(str[i]==estr[cnt]){ //判断estr中每个字符在str中能不能按顺序得到
cnt++;
if(cnt==n2) return true; //判断完毕
}
}
}
return false;
}
int main(){
// freopen("1.txt","r",stdin);
while(~scanf("%s%s",str,estr)){ //str为给初始的字符串,estr为你要生成的字符串
n=strlen(str);
n2=strlen(estr);
memset(mark,-1,sizeof(mark));
memset(per,0,sizeof(per));
for(int i=0;i<n;i++) scanf("%d",per+i);
int l=0,r=n-1;
int mid=n-1;
mid2=0;
while(r-l>1||mid!=l){ //考虑r-l==1的特殊情况,其实应该有更好的写法,但做的时候还不熟练,先这么写了
mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
mid2=mid;
}
if(r==0) printf("0\n");
else
printf("%d\n",l+1);
}
return 0;
}
Median *****
题意: 给一个n个数的数列,计算数列里各个数之间的差值的绝对值,形成一个新数列,求新数列的中位数
思路: 二分+贪心, 将初始序列从小到大排序,每次判断大于mid的值是否大于n*(n-1)个,细节见代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <set>
using namespace std;
vector<double> s;
int n;
int base;
bool check(double mid){
int ans=0;
vector<double>::iterator it1,it2;
for(it1=s.begin();it1!=s.end();it1++){
double l=(*it1)+mid;
it2=lower_bound(s.begin(),s.end(),l); //找到差值大于mid的第一个数的位置,则这个数之后的所有数差值都大于mid
if(it2!=s.end()); //如果有差值大于mid的数
ans+=n-(it2-s.begin());
}
return ans>=base+1;
}
int main(){
//freopen("1.txt","r",stdin);
double l,r;
while(~scanf("%d",&n)){
s.clear();
l=r=0;
for(int i=0;i<n;i++){
int t;
scanf("%d",&t);
s.push_back(t);
r=max(r,(double)t);
}
sort(s.begin(),s.end());
base=n*(n-1)/4; //判断mid的时候,大于mid的数的个数应该为 base个,这样mid才为中间的数
double mid;
while(r-l>0.5){
mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%d\n",(int)r);
}
return 0;
}
Gourmet and Banquet
题意:有N份菜,分别在[ai,bi]时间段内有供应,一位美食家想吃到每样菜,并且吃每样菜的时间要相同(吃每道菜的次数不限,比如可在a1-a2时间吃A菜,a3-a4时间再吃一次A菜,这样吃A菜的总时间为a4-a3+a2-a1)。求美食家能享受菜品的最大总时间。
思路: 与上面两题基本没啥差别,设mid 贪心判断是否可行即可,代码在学校的vjudge上
类型二:求最大最小值,最小最大值
River Hopscotch (最小值最大化)
题意: 河中有n+1个石头,已知它们到起点的距离,第n+1个是终点(不能动)。怎样去掉这n个石头中的m个,其间距的最小值可以达到最大。
思路: 二分答案+贪心
每一次贪心 距离前一个石头小于mid的石头都需要被拿走,若终点的石头不能满足,或者一共需要拿走的石头超过m个,则false
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <string>
using namespace std;
int rock[50010];
int L,m,n;
bool check(double mid){
int l=0;
int cnt=0;
for(int i=0;i<=n;i++){
if(rock[i]<l+mid){
cnt++;
if(cnt>m||i==n) return false; //需要拿走的超过m个,或者这个需要拿走的石头在终点,不能被拿走
}
else
l=rock[i];
}
return true;
}
int main(){
while(~scanf("%d%d%d",&L,&n,&m)){
memset(rock,0,sizeof(rock));
for(int i=0;i<n;i++)
scanf("%d",rock+i);
sort(rock,rock+n);
rock[n]=L;
double l=0,r=L;
double mid=r;
while(r-l>0.001){ //莫名喜欢用double 型的精度,注意check的时候怎么对mid的数取整
mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%d\n",(int)r);
}
return 0;
}
Monthly expense (最大值最小化)
题意: 给你一段数列,将其划分成连续的m段,求怎样划分使m段中最大的数列和 最小
思路: 同上一题 二分+贪心 每一次判断 细节见代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
using namespace std;
int day[100100];
int n,m;
bool check(double mid){
int l=0;
int cnt=1;
for(int i=0;i<n;i++){
if(l+day[i]>mid){
cnt++;
l=day[i];
if(day[i]>mid) return false; // 如果单独一天的资金就超过了mid 的话,false
if(cnt>m) return false; //需要的划分大于m
}
else
l=l+day[i];
}
return true;
}
int main(){
double l,r;
while(~scanf("%d%d",&n,&m)){
l=r=0;
memset(day,0,sizeof(day));
for(int i=0;i<n;i++){
scanf("%d",&day[i]);
r+=day[i];
}
double mid=r;
while(r-l>0.001){ //哈哈哈对double 的狂热爱好
mid=(r+l)/2;
if(check(mid))
r=mid;
else
l=mid;
}
printf("%d\n",(int)r); //注意不能为l
}
return 0;
}
类三:最大化平均值
Dropping tests (找公式)
题意: 给你数组a,b 从中抛掉k对数据使下列公式最大
思路: 设mid为最大值,将这个公式分解,按照100*a[i]-mid*b[i] 的值从大到小排序,取出前面n-k个值,判断大于0,则true
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
using namespace std;
long long a[1010],b[1010];
int n,k;
double c[1010];
bool check(double mid){
for(int i=0;i<n;i++)
c[i]=100.0*a[i]-mid*(double)b[i];
sort(c,c+n);
double ans=0;
for(int i=k;i<n;i++) //这里是从小到大排序,取最后n-k个数
ans+=c[i];
return ans>=0;
}
int main(){
while(~scanf("%d%d",&n,&k)&&n){
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=0;i<n;i++) scanf("%lld",&a[i]);
for(int i=0;i<n;i++) scanf("%lld",&b[i]);
double l=0;
double r=100;
double mid=r;
while(r-l>0.0001){
mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%d\n",(int)(r+0.5)); //注意答案要求四舍五入
}
return 0;
}
复杂点的?
Exams
题意: 给你n个考试科目编号1~n以及他们所需要的复习时间ai;(复习时间不一定要连续的,可以分开,只要复习够ai天就行了) 然后再给你m天,每天有一个值di; 其中,di==0代表这一天没有考试(所以是只能拿来复习的); 若di不为0,则di的值就代表第i天可以考编号为di的科目 ;(当然这一天也可以不考而拿来复习) 。 问你最少能在第几天考完所有科目,无解则输出-1。
思路: 代码即思路
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
typedef long long LL;
int day[100100];
int a[100100];
int n,m;
map<int,int> mark;
bool check(int mid){
LL need=0;
int cnt=0;
mark.clear();
for(int i=mid-1;i>=0;i--){
if(day[i]!=0&&!mark.count(day[i]-1)){
cnt++;
int exam=day[i]-1;
need+=a[exam];
mark[exam]=1;
}
else
if(need>0) need--;
}
if(cnt==m&&need==0) return true;
return false;
}
int main(){
// freopen("1.txt","r",stdin);
while(~scanf("%d%d",&n,&m)){
memset(day,0,sizeof(day));
memset(a,0,sizeof(a));
int l=1;
for(int i=0;i<n;i++) scanf("%d",&day[i]);
for(int i=0;i<m;i++) {
scanf("%d",&a[i]);
}
int r=n;
//l--;
int mid;
bool res=false;
while(r>l){
mid=(l+r)/2.0;
//printf("A %d %d %d %d\n",r,l,mid,n);
if(check(mid)){
// printf("TT %lf %lf %lf\n",l,r,mid);
res=true;
r=mid;
}
else
l=mid+1;
}
if(check(l))
printf("%d\n",l);
else if(check(r))
printf("%d\n",r);
else
printf("-1\n");
}
return 0;
}
Primary X-Subfactor Series **********
题意:
定义:subfactor:
1.v为u的子串
1)不含前导0
2)不能乱序
3)不能自己添加数字
4)至少删除一个数字
2.v为u的因数:u%v==0
3.v > 1
给出一个数字n(不超过10亿),每次删去一个他的subfactor,直到没有subfactor。
使得删减次数最多。如果存在次数一样则输出字典序最小的那个序列
思路: 不知道怎么二分,刚好学了紫皮上二进制表示子集那节,发现可以用递归+记忆花搜索做,就写了试试看,居然过了= =
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
using namespace std;
map<int,int> pre; //记录每一个数后面一个subfactor
map<int,int> cont; //记录每个数对应的最长subfactor sequencn的长度
int calcu(int n){ //求n的位数
int len=0;
while(n){
n/=10;
len++;
}
return len;
}
int shift(int n,int k,int op){ //op为1时表示需要考虑前导零,如在计算subfactor的时候,op为0时 不需要考虑,直接输出n的k排列时的值
long long m;
int ans;
ans=0,m=1; //m初始化为1,防止若ans最后有0的时候被跳过 ***
while(k){
if(k&1)
m=m*10+n%10;
k>>=1;
n/=10;
}
int mm=m;
while(m!=1){
ans=ans*10+m%10;
m/=10;
}
if(mm%10==0&&ans&&op) return -1;
return ans;
}
void progress(int N,int K,int& n,int& k){
n=k=0;
n=shift(N,K,0);
k=calcu(n);
k=(1<<k)-1;
return;
}
int solve(int N,int K){
int n,k;
progress(N,K,n,k); //将K排列的N 转换成满排列的n,k的各位都为1
if(cont[n]>0) return cont[n]; //记忆化,注意清零。 记忆化之后程序运行速度一下子从十几秒提升至不到一秒…… orz
pre[n]=-1; //初始化pre[n]为-1
cont[n]=1;
if(n==0) { //n为0时特殊处理
pre[0]=-1;
return cont[0];
}
for(int i=1;i<k;i++){
int m=shift(n,i,1); //求n取i排列时候的值
if(m>0&&m!=1&&n%m==0){
int t=solve(n,k^i); //注意 k^i 的意义
if(cont[n]<t+1){
cont[n]=t+1;
pre[n]=shift(n,k^i,0); //pre 记录下一个subfactor的位置,为-1时表示到底啦
}else if(cont[n]==t+1){
pre[n]=min(pre[n],shift(n,k^i,0)); //pre[n]记录字典序最小的subfactor的值
}
}
}
return cont[n];
}
int main(){
//freopen("1.txt","r",stdin);
int n;
while(~scanf("%d",&n)&&n){
pre.clear();
cont.clear();
int len=calcu(n); //计算 n的数位
int k=(1<<len)-1;
solve(n,k);
while(1){
printf("%d", n);
if(pre[n]==-1) {printf("\n"); break;}
else{
printf(" ");
n=pre[n];
}
}
}
return 0;
}
还要继续加油呀少年~