深基刷题记录(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

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