一共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;
}
還要繼續加油呀少年~