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