深基刷題記錄(2)

P1255 數樓梯

在這裏插入圖片描述

#include <bits/stdc++.h> 
using namespace std;
int n,a[5005][5005],len=1;
void hp(int k)
{
	for(int i=1;i<=len;i++)
		a[k][i]=a[k-2][i]+a[k-1][i];
	for(int i=1;i<=len;i++)
		if(a[k][i]>=10)
		{
			a[k][i+1]+=a[k][i]/10;
			a[k][i]%=10;
			if(a[k][len+1]) len++; //竟然可以改全局變量(不過只能在本函數中持續一輪)
		}
}
int main()
{
	cin>>n;
	a[1][1]=1;
	a[2][1]=2;
	for(int i=3;i<=n;i++)
		hp(i);
	for(int i=len;i>0;i--)
		cout<<a[n][i];
	return 0;
}

斐波那契 + 高精度,臺階和高精度都從 1 開始計數;有用到 len,在末尾進位時,一般持續運算時才需要

P1002 過河卒

在這裏插入圖片描述
在這裏插入圖片描述

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ull unsigned long long
using namespace std;
const int fx[] = {0, -2, -1, 1, 2, 2, 1, -1, -2};
const int fy[] = {0, 1, 2, 2, 1, -1, -2, -2, -1};
//馬可以走到的位置
int bx,by,mx,my;
ull f[30][30];//f[i][j]代表從A點到(i,j)會經過的線路數
bool s[30][30];//判斷這個點有沒有馬盯着
int main(){
    scanf("%d%d%d%d", &bx, &by, &mx, &my);
    bx += 2; by += 2; mx += 2; my += 2;
    //座標+1以防越界
    f[2][2] = 1;//初始化
    s[mx][my] = 1;//標記馬的位置
    for(int i = 1; i <= 8; i++)
        s[ mx + fx[i] ][ my + fy[i] ] = 1;
    for(int i = 2; i <= bx; i++){
        for(int j = 2; j <= by; j++){
            if(s[i][j])continue;
            f[i][j] = max( f[i][j] , f[i - 1][j] + f[i][j - 1] ); 
            //狀態轉移方程
        }
    }
    printf("%llu\n", f[bx][by]);
    return 0;
} 

在這裏插入圖片描述
每個座標加 2 是防止馬盯着的位置越界

P1044 棧

在這裏插入圖片描述
在這裏插入圖片描述

//第一種方法: 遞歸

#include<cstdio>
#define MAX_N 20
#define ll long long
using namespace std;
int n;
ll f[MAX_N][MAX_N];
ll dfs(int i,int j)
{
	if(f[i][j]) return f[i][j]; //若記錄過則返回本值 
	if(i==0)return 1; //邊界 
	if(j>0) f[i][j]+=dfs(i,j-1);
	f[i][j]+=dfs(i-1,j+1);
	return f[i][j];
}
int main()
{
	scanf("%d",&n);
	printf("%lld",dfs(n,0));
	return 0;
}



//第二種方法: 遞推

#include<cstdio>
#define MAX_N 20
#define ll long long
using namespace std;
int n;
ll f[MAX_N][MAX_N];
int main()
{
	scanf("%d",&n);
	for(int i=0;i<=n;i++)
	{
		f[0][i]=1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			if(i==j)f[i][j]=f[i-1][j];
			else f[i][j]=f[i][j-1]+f[i-1][j];
		}
	}
	printf("%lld",f[n][n]);
	return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
第一種方法以隊列裏的待排數(i)作爲參考情況,每次減少(含兩種情況),當減少爲 0 時到達臨界點返回 1,棧中的數爲(j
第二種方法 i 表示進棧,j 表示出棧,而且:

f[i,j]==f[i+1,j] 與 f[i,j]==f[i,j-1] 可以推出 f[i,j]==f[i+1,j-1]

從大到小用 dfs,從小到大用 dp

P1024 一元三次方程求解

在這裏插入圖片描述

#include<cstdio>
double a,b,c,d;
double fc(double x)
{
    return a*x*x*x+b*x*x+c*x+d;
}
int main()
{
    double l,r,m,x1,x2;
    int s=0,i;
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);  //輸入
    for (i=-100;i<100;i++)
    {
        l=i; 
        r=i+1;
        x1=fc(l); 
        x2=fc(r);
        if(!x1) 
        {
            printf("%.2lf ",l); 
            s++;
        }      //判斷左端點,是零點直接輸出。
                        
                        //不能判斷右端點,會重複。
        if(x1*x2<0)                             //區間內有根。
        {
            while(r-l>=0.001)                     //二分控制精度。
            {
                m=(l+r)/2;  //middle
                if(fc(m)*fc(r)<=0) 
                   l=m; 
                else 
                   r=m;   //計算中點處函數值縮小區間。
            }
            printf("%.2lf ",r);  
            //輸出右端點。
            s++;
        }
        if (s==3) 
            break;             
            //找到三個就退出大概會省一點時間
    }
    return 0;
}

二分法,r-l>=0.001 做的很棒,最重要的是輸入的是 double 而不是 int(坑點)

P1028 數的計算

在這裏插入圖片描述

#include<bits/stdc++.h>//萬能頭文件
using namespace std;
int n;
int f[1001];//存每一位數的種類
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)//1-n的遞推
	{ 
        for(int j=1;j<=i/2;j++)
            f[i]+=f[j]; //每一位疊加,遞推走起
    	f[i]++; //加上本身
    }
    cout<<f[n];//輸出n的種類
    return 0;
}

在這裏插入圖片描述
難點就是尋找遞推關係

P1464 Function

在這裏插入圖片描述
在這裏插入圖片描述

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll rpt[25][25][25];
ll w(ll a,ll b,ll c)
{
    if(a<=0||b<=0||c<=0) return 1;
    else if(rpt[a][b][c]!=0) return rpt[a][b][c]; //這叫記憶
    else if(a>20||b>20||c>20) rpt[a][b][c]=w(20,20,20);
    else if(a<b&&b<c) rpt[a][b][c]=w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c);
    else rpt[a][b][c]=w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);
    return rpt[a][b][c]; //這叫返回值
}
int main()
{
    ll a,b,c;
    while(scanf("%lld%lld%lld",&a,&b,&c)==3){
        memset(rpt,0,sizeof(rpt));
        if(a==-1&&b==-1&&c==-1) break;
        printf("w(%lld, %lld, %lld) = ",a,b,c);
        if(a>20) a=21;
        if(b>20) b=21;
        if(c>20) c=21;
        printf("%lld\n",w(a,b,c));
    }
    return 0;
}

坑點:一開始的判斷語句如果只寫if(f[x][y][z])就炸了因爲不能訪問數組負數下標
亮點:每次0~20以內的答案記錄下來,下一次遞歸時如果rpt(x,y,z)有記錄就直接輸出就行

P1928 外星密碼

在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
string read()
{
	int n;
	string s="",s1;
	char c;
	while (cin>>c)//一直讀入字符,直到Ctrl+z
	{
		if (c=='[')
		{
			cin>>n;//讀入D
			s1=read();//讀入X
			while (n--) s+=s1;//重複D次X
            //注:上面不能寫成while (n--) s+=read();
		}
		else 
		{
			if (c==']') return s;//返回X
		    else s+=c;//如果不是'['和']',那就是X的一個字符,所以加進X
		}
	}
}
int main()//巨短主函數
{
	cout<<read(); 
	return 0;
}

挺棒的一題,以前有做過類似的,現在撿起來了

P1164 小A點菜

在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+10;
int v[maxn],f[maxn];
int main(){
    int n,m;
    cin>>n>>m;
    f[0]=1;
    for(int i=1;i<=n;++i)    
        cin>>v[i];//讀入 價值
    for(int i=1;i<=n;++i)
        for(int j=m;j>=v[i];--j)
            f[j]+=f[j-v[i]];//現在的花費+=我不點這個菜的時候的花費
    cout<<f[m]<<endl;//最後把最後一個點的花費輸出來就可以了
    return 0;
}

01揹包標準打法,但又有點不同,下次來看自己寫一下

P1990 覆蓋牆壁

死活搞不懂的 dp,下次一定

P3612 [USACO17JAN]Secret Cow Code S

在這裏插入圖片描述

#include <bits/stdc++.h>
using namespace std;
string s;
long long n,num,i;
int main()
{
	//代碼部分借鑑1樓 
	cin>>s>>n;
	num=s.length(); 
	while(num<n)
	{
		i=num;
		while(n>i)	i*=2;//求出當前剛好包括n位置的串長 
		i=i/2;//得到當前串的一半長 
	//	if(n==i+1)	n=i;特殊處理,假如這裏n位置是i+1
	//那麼經過下面這步操作後,變成了0,那我們下面對0特判 
		n-=(i+1); 
		if(n==0)	n=i;
	}
	cout<<s[n-1];
}

在這裏插入圖片描述

P1228 地毯填補問題

死活搞不懂,這類題絕對不留給9月

P1429 平面最近點對(加強版)

我感覺我就是個智障

P1259 黑白棋子的移動

P1010 冪次方

在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
void dg(int x)
{
	int y;
	if(x==0) return;
	for(int i=0;i<=15;i++)
	{
		y=i;
		if(pow(2,i)>x) //這個是用來找到比 n小的 2次方中最大的
		{
			y--;
			break; //跳出循環
		}
	}
	if(y==0) cout<<"2(0)";
	if(y==1) cout<<"2";
	if(y>1)
	{
		cout<<"2(";
		dg(y);
		cout<<")";
	}
	if(x!=pow(2,y))
	{
		cout<<"+";
		dg(x-pow(2,y)); //遞歸剩餘的
	}
}
int main()
{
    int n;cin>>n;
    dg(n);
    return 0;
}

P1803 凌亂的yyy / 線段覆蓋

在這裏插入圖片描述

#include<bits/stdc++.h>//(萬能庫)
struct px{//(定義一個結構體數組,分別儲存開始時間和結束時間)
int a;//(開始時間)
int b;//(結束時間)
}x[2000000];
bool cmp(px x,px y){//(不管開始時間,直接按照結束時間排序)
return x.b<y.b;
}
using namespace std;
int main(){
int n,sum=1,mi;
scanf("%d",&n);
for(int i=1;i<=n;i++)
cin>>x[i].a>>x[i].b;//(讀入數據)
sort(x+1,x+n+1,cmp);//(排序)
mi=x[1].b;//(無腦記錄第一個值)
int j=1;
while(j<=n)//(未優化的超長循環)
{
    j++;
    if(x[j].a>=mi) {//(找到符合要求的比賽,記錄,參加)
    sum++;//(計數)
    mi=x[j].b;}
}
cout<<sum;//(輸出)
return 0;//(功德圓滿)
}

從結束時間從小到大排序,以第二場開始時間小於等於上一場結束時間作爲判斷,增加比賽場次

P1090 合併果子 / [USACO06NOV] Fence Repair G

在這裏插入圖片描述
在這裏插入圖片描述

// 第一種方法:桶排序 O(n)算法

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int k,x,num,n1,n2,a1[30001],a2[30001],t[20001],w,sum;
int main()
{
	scanf("%d",&num);
	memset(a1,127/3,sizeof(a1));
	memset(a2,127/3,sizeof(a2));
	for (int i=1;i<=num;i++)
	{
		scanf("%d",&x);
		t[x]++;//桶
	}
	for (int i=1;i<=20000;i++)
	{
		while (t[i])//桶排序
		{
			t[i]--;
			a1[++n1]=i;
		}
	}
	int i=1,j=1;
	k=1;
	while (k<num)
		{
		if (a1[i]<a2[j])//取最小值
		{
			w=a1[i];
			i++;
		}
		else
		{
			w=a2[j];
			j++;
		}
		if (a1[i]<a2[j])//取第二次
		{
			w+=a1[i];
			i++;
		}
		else
		{
			w+=a2[j];
			j++;
		}
		a2[++n2]=w;//加入第二個隊列
		k++;//計算合併次數
		sum+=w;//計算價值
	}
	printf("%d",sum);
}


//第二種方法:優先隊列

#include<bits/stdc++.h>
using namespace std;
int n,x,ans;
priority_queue<int,vector<int>,greater<int> >q;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>x,q.push(x);
	while(q.size()>=2){
		int a=q.top(); q.pop();
		int b=q.top(); q.pop();
		ans+=a+b;
		q.push(a+b);
	}
	cout<<ans<<endl;
	return 0;
}

思路很清晰,基本看一下第二種方法的代碼就明白了

P3817 小A的糖果

在這裏插入圖片描述
在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
long long n,x;
int main()
{
	long long sum=0;//計數器,n,x;
	cin>>n>>x;//輸入
	long long a[n+1];
	cin>>a[1];//處理第一個單獨超限。
	if(a[1]>x)
		{
		sum+=a[1]-x;//增加喫的量
		a[1]=x;//a[i]>=x,要喫的最少,即是a[i]=x;
		}
	for(int i=2;i<=n;i++)
		{
		cin>>a[i];//輸入
		if(a[i]+a[i-1]>x)//照例處理
			{
			sum+=a[i]+a[i-1]-x;
			a[i]=x-a[i-1];
			}
		}
	cout<<sum;//輸出
	return 0;//養成好習慣
}

思路非常牛逼,先對第一個分析是否超限,若是則總數加上超出的部分,然後從後一個開始每次和前一個加起來和限度對比,超限減去超限數即可(因爲前一個永遠是處理好的)

P1106 刪數問題

在這裏插入圖片描述

#include <stdio.h>
#include <string.h>
char c[260];
int main()
{
    int len,i,j,s;
    scanf("%s%d",c,&s);
    len=strlen(c);
    while(s--)
	{
        for(i=0;i<=len-2;i++)
            if(c[i]>c[i+1])
			{
                for(j=i;j<=len-2;j++)
                    c[j]=c[j+1];
                break;
            }
        len--;//此處位置寫錯,之前寫在if內部
    }
    i=0;
    while(i<=len-1&&c[i]=='0')i++;//處理前導0 
    if(i==len)printf("0");
    else
        for(j=i;j<=len-1;j++)
            printf("%c",c[j]); 
    return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
刪山峯的方法就是:只要前面一位比後面大,它就是山峯(因爲最前面已經被刪了)

P1478 陶陶摘蘋果(升級版)

在這裏插入圖片描述
在這裏插入圖片描述

//方法一 :搜索 +剪枝 +記憶優化
#include<iostream>
#include<algorithm> 
using namespace std;
int n,s,a,b,ans;
bool visit[5005][1001];
int mem[5005][1001];
struct apple{
    int xi,yi;
}ap[5005];
int dfs(int num,int rest){
    if(num>n||ap[num].xi>a+b) return 0;//當搜索到夠不到的蘋果後,就不再繼續向下搜索了
    if(visit[num][rest]) return mem[num][rest];
    visit[num][rest]=true;
    int maxn=dfs(num+1,rest);
    if(ap[num].xi<=a+b&&rest>=ap[num].yi){
        int  t=dfs(num+1,rest-ap[num].yi)+1;
        maxn=t>maxn?t:maxn;
    }
    return mem[num][rest]=maxn;
}
int cmp(apple x,apple y){
    return x.xi<y.xi;
}
int main(){
    cin>>n>>s>>a>>b;
    for(int i=1;i<=n;i++){
        cin>>ap[i].xi>>ap[i].yi;
    }
    sort(ap+1,ap+n+1,cmp);//按照高度從矮到高排序
    cout<<dfs(1,s);
    return 0;
} 

//方法二 :dp揹包
#include<iostream>
#include<algorithm> 
using namespace std;
int n,s,a,b,ans;
int mem[5005][1001];
struct apple{
    int xi,yi;
}ap[5005];
int cmp(apple x,apple y){
    return x.xi<y.xi;
}
int main(){
    cin>>n>>s>>a>>b;
    for(int i=1;i<=n;i++){
        cin>>ap[i].xi>>ap[i].yi;
    }
    sort(ap+1,ap+n+1,cmp);//按照高度從矮到高排序
    for(int i=1;i<=n;i++)
    	for(int j=1;j<=s;j++)
    	{
    		mem[i][j]=mem[i-1][j];
    		if(ap[i].xi<=a+b&&j>=ap[i].yi)
    			mem[i][j]=max(mem[i][j],mem[i-1][j-ap[i].yi]+1);
		}
	cout<<mem[n][s];
    return 0;
} 

//方法三 :貪心
#include<iostream>
#include<algorithm> 
using namespace std;
int n,s,a,b,x_,y_,can,rest,ans;
struct apple{
    int xi,yi;
}ap[50005];
int cmp(apple x,apple y){
    return x.yi<y.yi;
}
int main(){
    cin>>n>>s>>a>>b;
    for(int i=1;i<=n;i++){
        cin>>x_>>y_;
        if(x_<=a+b){
            can++;
            ap[can].xi=x_;
            ap[can].yi=y_;
        }
    }
    sort(ap+1,ap+can+1,cmp);
    rest=s;
    ans=0;
    for(int i=1;rest>=ap[i].yi&&i<=can;i++){
        ans++;
        rest-=ap[i].yi;
    }
    cout<<ans;
    return 0;
} 

簡單題用來回顧所學知識(簡單搜索,簡單dp,貪心)

P5019 鋪設道路

在這裏插入圖片描述
在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
int n,a[100005];
long long ans=0;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)     cin>>a[i];
	for(int i=2;i<=n;i++)     if(a[i]>a[i-1]) ans+=a[i]-a[i-1];
	cout<<ans+a[1];
	return 0;
}

無敵貪心,我想懂了
結果就是 每個上升序列的差,加第一個上升序列的最小元素,爲什麼呢?因爲這樣可以保證減去第一個上升序列的最小元素,所有序列都歸零,如何保證?因爲若第一個上升序列的最大值緊挨着第二個上升序列的最小值,第一個上升序列的最大值減小則帶動後面的序列一起減小,肯定會滿足所有的序列最小值 小於 第一個序列的最小值

(P1969 積木大賽) 與這題類似

P1094 紀念品分組

在這裏插入圖片描述
在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
int W,ans=0;
int n,a[30001];
int l,r,i;
int main()
{
    scanf("%d%d",&W,&n);
    for(i=1;i<=n;i++)
      scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    l=1;  r=n;
    while(l<=r)//一定要有等號。
    {
        if(a[l]+a[r]<=W)   //一定要有等號。
          l++,r--,ans++;
        else
          r--,ans++;   //貪心過程
    }
    printf("%d",ans);
    return 0;
}

最小匹配最大,如果不行就最大的單獨分一組,這樣貪心下來就是最少分的組了。證明如下:
總不能讓最小單獨分一組吧

P1080 國王遊戲

明白題意(算是),但不知道怎麼寫(高精度除法和乘法)

P4447 [AHOI2018初中組]分組

在這裏插入圖片描述
在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
typedef map<int,int>::iterator it;
map<int,int> m;
int main()
{
	int n,ans=INT_MAX;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		int t;
		scanf("%d",&t);
		m[t]++;
		//記錄圖像。
	}
	while(!m.empty())
	{
		it i=m.begin(),j=m.begin();
		(*i).second--; //代表所對應的數--,而不是 i-- 
		int t=1;
		for(j++;j!=m.end()&&(*j).first==(*i).first+1&&(*j).second>(*i).second;i++,j++)
		{
   			t++;
			(*j).second--;
		}
		//若 i,j 所對應的能力值是連續的,且 i 對應的那一列高度不高於 j,則繼續畫線。
		i=m.begin();
		while(i!=m.end()&&(*i).second==0)
		{
			m.erase((*i++).first); //代表 i++,而不是所對應的數++ 
		}
		//高度降爲 0 後直接刪除,便於計算。
		if(t<ans)
			ans=t;
		//記錄答案。
	}
	printf("%d",ans);
	return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
當右邊方塊低於左邊就停止畫線(每次畫玩完線後記得刪除已畫線的方塊)

爲了使迭代器 it 正常的到達末尾,刪去的應該是第二個元素(j),若刪去第一個元素(i),則 it 到了末尾就無法刪去 j

P1873 砍樹

在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
long long n,bz,s=0,mid,leftt,longest,trees[1000008];
int main()
{
    scanf("%lld%lld",&n,&bz); 
    for(int i=1;i<=n;i++) 
    {
        scanf("%lld",&trees[i]);
        longest=max(longest,trees[i]);//找到最長木材 
    }
    while(leftt<=longest)
    {
        mid=(leftt+longest)/2; //從中間點開始作爲伐木機高度
        s=0; 
        for(int i=1;i<=n;i++) 
			if(trees[i]>mid) //樹的高度大於伐木機高度 
				s+=trees[i]-mid; //高的部分累加 
        if(s<bz) //木材不足 
			longest=mid-1;//在左邊搜 減小高度增加木材 
		else 
			leftt=mid+1;//在右邊搜 增加高度減小木材 
    }
    cout<<leftt-1; 
    return 0;
}

最後的 leftt-1 其實相當於 longest,因爲只有當 leftt+1==longest 時才能退出循環

P1678 煩惱的高考志願

在這裏插入圖片描述
在這裏插入圖片描述

//方法一:優先隊列
#include<algorithm>
#include<iostream>
#include<cmath>//使用abs絕對值函數 
#include<queue>//使用優先隊列 
using namespace std;
#define re register//register加速程序運行速度,不懂百度,我也解釋不大了,不懂勿用 
const int maxn=100001;
priority_queue<int,vector<int>,greater<int> >a;//優先隊列,學生成績 (小到大) 
int main(){
    int b[maxn],m,n,k=1,sum=0;//b是學校錄取線,sum是不滿意度,k是目前走到的學校 
    cin>>m>>n;
    for(re int i=1;i<=m;i++)cin>>b[i];
    for(re int i=1;i<=n;i++){
        re int x;
        cin>>x;
        a.push(x);
    }
    sort(b+1,b+m+1);//把學校的錄取線從小到大排序 
    for(re int i=1;i<=n;i++){//n個學生,從小到大 
        re int x=a.top(),p=abs(x-b[k]);//x爲此學生分數,p存的是選取k學校的不滿意值 
        a.pop();//彈出 
        while(abs(x-b[k+1])<=p){//如果下一個學校更小,選下一個(注意:一定是小於等於,不明白私信我) 
            k++;
            p=abs(x-b[k]);
        }
        sum+=p;//加上這個學生的不滿意值 
    }
    cout<<sum;
    return 0;
}

//方法二:upper_bound
#include<bits/stdc++.h>
using namespace std;
int a,b,c[100002],d,e,f,g,h,i,j,k,l;
long long ans;
int main()
{
    cin>>a>>b;
    for(i=1;i<=a;i++)
        cin>>c[i];
    sort(c+1,c+a+1);//先排序一下
    for(i=1;i<=b;i++)
    {
        cin>>d;
        e=upper_bound(c+1,c+1+a,d)-(c+1);//返回查詢到的位置
        if(e==a+2)
        	ans+=d-c[a];//特判比所有數都大的情況
        if(e==0)
        	ans+=c[1]-d;//特判比所有數都小的情況
        else
            ans+=min(abs(d-c[e]),abs(d-c[e+1]));//當前與前一個數 
    }
    cout<<ans;
    return 0;
}

方法一:while 裏面之所以是小於等於,是因爲若是小於,則在代表 k 不變,那麼以後的數字也不會變,顯然是不行的
方法二:注意特判

P2440 木材加工

在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
int a[100005];//存樹 
int sum;//記錄所有樹加起來的總長度 
int n,k;
//判斷函數 
int pd(int x) {
	int num=0;

	for(int i=1; i<=n; i++) {
		num+=(a[i]/x); //注意是每根木頭除以長度的和,
		               // 如果用總長度去求則可能有些木頭過短需要丟掉,但是還是被算入其中 
		if(num>=k) return true;
	}

	return false;

}
//二分函數 
int cut(int l,int r) {
	if(r<=l) return l;
	int mid=(r+l)/2 + rand()%2;
	if(mid==0) return 0;//如果小於一直接返回0 
	if(pd(mid))
		return cut(mid,r);

	return cut(l,mid-1);
}

int main() {
	cin>>n>>k;
	for(int i=1; i<=n; i++) {
		cin>>a[i];
		sum+=a[i];
	}
	
	int r,l;
	r=sum/n;
	l=0;
	
	int ans=cut(l,r);
	if(ans==0) cout<<0;
	else cout<<ans;
	return 0;
}

二分把我難吐了

P2678 跳石頭

在這裏插入圖片描述
在這裏插入圖片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define maxn 500010
using namespace std;
int d,n,m;
int a[maxn];
int l,r,mid,ans;
inline int read(){//我喜歡快讀
    int num = 0;
    char c;
    bool flag = false;
    while ((c = getchar()) == ' ' || c == '\n' || c == '\r');
        if (c == '-') flag = true;
    else
        num = c - '0';
    while (isdigit(c = getchar()))
    num = num * 10 + c - '0';
    return (flag ? -1 : 1) * num;
}

bool judge(int x){//judge函數,x代表當前二分出來的答案
    int tot = 0;//tot代表計數器,記錄以當前答案需要移走的實際石頭數
    int i = 0;//i代表下一塊石頭的編號
    int now = 0;//now代表模擬跳石頭的人當前在什麼位置
    while (i < n+1){//千萬注意不是n,n不是終點,n+1纔是
        i++;
        if (a[i] - a[now] < x)//判斷距離,看二者之間的距離算差值就好
            tot++;//判定成功,把這塊石頭拿走,繼續考慮下一塊石頭
        else
            now = i;//判定失敗,這塊石頭不用拿走,我們就跳過去,再考慮下一塊
    }
    if (tot > m)
        return false;
    else
        return true;
}

int main(){
    d = read();//d代表總長度,也就是右邊界
    n = read();//n塊石頭
    m = read();//限制移走m塊,思考的時候可別被這個m限制
    for (int i=1;i<=n;i++)
        a[i] = read();
    a[n+1] = d;//敲黑板劃重點,再強調一遍,n不是終點
    l = 1;//l和r分別代表二分的左邊界和右邊界
    r = d;
    while (l <= r){//非遞歸式二分正常向寫法,可理解爲一般框架
        mid = (l+r) / 2;//這再看不出是啥意思可以退羣了
        if (judge(mid)){//帶入judge函數判斷當前解是不是可行解
            ans = mid;
            l = mid + 1;//走到這裏,看來是可行解,我們嘗試看看是不是有更好的可行解
        }
        else
            r = mid - 1;//噫,你找了個非法解,趕緊回到左半邊看看有沒有可行解
    }
    cout << ans << endl;//最後的ans絕對是最優解
    return 0;
}

代碼挺簡單的,就是不太容易想得到
其實這裏的 ans == l == r

P3853 [TJOI2007]路標設置

在這裏插入圖片描述
在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
int l,n,k;
int a[100005];
int L,R; 
bool check(int dis){//判斷mid是否滿足條件的函數~~~
	int cnt=0;//記錄所用路標的個數
	for(int i=0;i<=n;i++){
		if(a[i+1]-a[i]>dis)
		{
			cnt+=(a[i+1]-a[i])/dis;  //添加的個數 
			if((a[i+1]-a[i])%dis==0)  //不僅包括小於的情況,還有等於的情況 
				cnt--;
		}
			
		if(cnt>k) return false;//不滿足條件
	}
	return true;//滿足條件
} 
int main(){
	scanf("%d%d%d",&l,&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	L=0;R=l;//表示懶得動腦qwq
	a[0]=0;//注意題目的坑點,前後都不能丟
	a[n+1]=l;
	int ans;
	while(L<=R){
		int mid=(L+R)/2;
		if(check(mid)) R=mid-1;
		else L=mid+1;
	}
	cout<<L;
} 

一個刪石頭,一個加路標,就很棒,而且都是標準模板,思路又很清楚(和創新),我的最愛

P1182 數列分段 Section II

在這裏插入圖片描述
在這裏插入圖片描述

#include<iostream>
using namespace std;
int n,m;
int lef,rig,mid;
int total,tim;
inline bool judge(int x,int a[]){
    total=0,tim=0;
    for(int i=0;i<n;i++){
        if(total+a[i]<=x)total+=a[i];
        else total=a[i],tim++;
    }
    return tim>=m;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    int a[n];
    for(int i=0;i<n;i++){
        cin>>a[i];
        rig+=a[i];
        lef=lef>a[i]?lef:a[i];
    }
    while(lef<=rig){
        mid=(lef+rig)/2;
        if(judge(mid,a))lef=mid+1;
        else rig=mid-1;
    }
    cout<<lef;
    return 0;
}

和上面兩題一樣的套路,記住 lef == rig ==ans,雖然這裏沒有 ans

P3743 kotori的設備

在這裏插入圖片描述
在這裏插入圖片描述

#include  <iostream>
using namespace std;
int n;//設備數量
double p;//充電器的充電速度
double a[200000],b[200000];
double lbound=0,rbound=1e10;
double sum=0; //需要的能量總和(驗證答案時)、所有設備的消耗能量速度總和(-1特判時)
int check(double ans){//驗證答案
	double q=p*ans;//充電器最多提供的能量
	sum=0;
	for(int i=0;i<n;i++){
		if(a[i]*ans<=b[i]){//若設備已有的能量大於使用時間需要的能量
			continue;//忽略該設備
		}
		sum+=(a[i]*ans-b[i]);//否則用充電器充電,使設備已有的能量等於使用時間需要的能量,並記錄需要的能量。
	}
	return sum<=q;//最後比較需要的能量總和和充電器最多提供的能量。
}
int main(){
	cin>>n>>p;
	for(int i=0;i<n;i++){
		cin>>a[i]>>b[i];
		sum+=a[i];
	}
	if(sum<=p){//若所有設備的消耗能量速度總和還是小於充電器的充電速度,輸出-1。
		cout<<-1.000000<<endl;
		return 0;
	}
	while(rbound-lbound>1e-4){
		double mid=(lbound+rbound)/2;
		if(check(mid)){
			lbound=mid;
			
		}else{
			rbound=mid;
			
		}
	}
	cout<<lbound<<endl;
	return 0;
}

與二分模板不相符的是,while 裏面是大於號,mid不用 +1 或 -1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章