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