8.2 暑期集训—— 二分法
2017-08-04 01:07
393 查看
一共10道题,除了最后一题没想到怎么用二分,最后用递归写出来之外,其他题目都可以归为几类经典的二分题,这次将题目按类分出
具体按类分比较基础的讲解可以参考《挑战程序设计》二分法一章
有两题有点不太熟的放在的最后
可以跳到最后看 primary X-subfactor series [b]*********[/b]
和Exams [b]*******[/b]
每个人只能从一个披萨(陷阱)中取一块,已知每个披萨的半径,每个人取的披萨面积一样,求可以取到的最大面积
误差要求不超过10^-3
思路: 二分面积,判断 求每个披萨能贡献多少块,总和大于人数则可行
注意: 精度的问题啊…… WA了好多次,有 两处 细节看代码注释
,每个数字对应字符串中一个位置,求最多按数组的顺序删多少个s中的字符,s 仍可以生成 t
思路: 二分位置,如果可行则二分后一段,否则前一段
想清楚二分比不二分优化在哪里
我不是很理解这有什么优化的地方…… 而且这个复杂度好像也不太会算啊 ORZ
思路: 二分+贪心, 将初始序列从小到大排序,每次判断大于mid的值是否大于n*(n-1)个,细节见代码
思路: 与上面两题基本没啥差别,设mid 贪心判断是否可行即可,代码在学校的vjudge上
思路: 二分答案+贪心
每一次贪心 距离前一个石头小于mid的石头都需要被拿走,若终点的石头不能满足,或者一共需要拿走的石头超过m个,则false
思路: 同上一题 二分+贪心 每一次判断 细节见代码
思路: 设mid为最大值,将这个公式分解,按照100*a[i]-mid*b[i] 的值从大到小排序,取出前面n-k个值,判断大于0,则true
思路: 代码即思路
定义:subfactor:
1.v为u的子串
1)不含前导0
2)不能乱序
3)不能自己添加数字
4)至少删除一个数字
2.v为u的因数:u%v==0
3.v > 1
给出一个数字n(不超过10亿),每次删去一个他的subfactor,直到没有subfactor。
使得删减次数最多。如果存在次数一样则输出字典序最小的那个序列
思路: 不知道怎么二分,刚好学了紫皮上二进制表示子集那节,发现可以用递归+记忆花搜索做,就写了试试看,居然过了= =
还要继续加油呀少年~
具体按类分比较基础的讲解可以参考《挑战程序设计》二分法一章
有两题有点不太熟的放在的最后
可以跳到最后看 primary X-subfactor series [b]*********[/b]
和Exams [b]*******[/b]
类型一:二分答案验证是否可行
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,每个数字对应字符串中一个位置,求最多按数组的顺序删多少个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 [b]*****[/b]
题意: 给一个n个数的数列,计算数列里各个数之间的差值的绝对值,形成一个新数列,求新数列的中位数思路: 二分+贪心, 将初始序列从小到大排序,每次判断大于mid的值是否大于n*(n-1)个,细节见代码
#include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <cmath> #include <vector> #include < 1382d 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 =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 [b]**********[/b]
题意:定义: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 >0) return cont ; //记忆化,注意清零。 记忆化之后程序运行速度一下子从十几秒提升至不到一秒…… orz pre =-1; //初始化pre 为-1 cont =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 <t+1){ cont =t+1; pre =shift(n,k^i,0); //pre 记录下一个subfactor的位置,为-1时表示到底啦 }else if(cont ==t+1){ pre =min(pre ,shift(n,k^i,0)); //pre 记录字典序最小的subfactor的值 } } } return cont ; } 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 ==-1) {printf("\n"); break;} else{ printf(" "); n=pre ; } } } return 0; }
还要继续加油呀少年~
相关文章推荐
- NBUT-2014暑期集训专题练习1 -> 二分法 N - N
- NBUT-2014暑期集训专题练习1 -> 二分法 A题
- NBUT-2014暑期集训专题练习1 -> 二分法E - E
- NBUT-2014暑期集训专题练习1 -> 二分法 J - J
- NBUT-2014暑期集训专题练习1 -> 二分法 H - H
- NBUT-2014暑期集训专题练习1 -> 二分法B - B
- NBUT-2014暑期集训专题练习1 -> 二分法L - L
- NBUT-2014暑期集训专题练习1 -> 二分法F - F
- 大一暑期集训训练赛一
- 2016暑期集训1C 最长回文
- 2016暑期集训10 A魏传之长坂逆袭
- 2016暑期集训16A强迫症
- HDOJ2009暑期集训公开赛(3)-ANARC2008
- 2132: 中南大学2017年ACM暑期集训前期训练题集(入门题)
- SHU 2013 暑期集训(7-16)解题报告
- 暑期集训之0和5
- 暑期集训之今年暑假不AC
- 暑期集训之Trailing Zeroes (III)
- 杭电暑期多校集训—Time To Get Up
- 杭电暑期多校集训—Euler theorem