ZZU ACM 基础算法 笔记(连载中)

ZZU ACM培训 基础算法 笔记2

郑大ACM day3

模拟

取模遍历

从中间某数b开始将数组遍历一周

for(int i=b;i<=n-1;i++){
    cout<<a[i]<<" ";
}
for(int i=0;i<b;i++){
    cout<<a[i]<<" "
}

以上为取模遍历。

相比普通按顺序分两段遍历,取模遍历明显更为简便。

for(int i-0;i<n;i++){
    cout<<a[(b+1)%n]<<" ";

枚举

百钱买百鸡典例

运用三层嵌套循环,并通过限制数量为整型,限制变量范围,从而逼近答案,减小时间复杂度.

减少枚举变量
  1. 解答中,除去最后一套循环。把for循环改成对小鸡数量的关系式表达,可以减少代码的复杂度,和时间复杂度
  2. 可以再进一步,利用方程组的形式,优化代码,减少至最后只有一个循环。
首尾相接凑2021

去掉z中间某段:

for(int i=0;i<n;i++){   //去掉的起点
    for(int j=i;j<n;j++)  //去掉的终点
}
/*s.substr(p,n)  返回从s的下标p开始的n个字符组成的字符串,如果n省略就取到底O(n)
little cpp  stl */
string t=s.substr(0,i)+s.substr(j+1 )
bool ok=false;
......
if(t=="2021"){
 ok=ture;
   }
cout<<(ok?"yes":"no")<<"\n"; //一个用来判断的小技巧

代码优化:

// 初始代码
bool ok=false;
for(int i=0;i<n;i++){   //去掉的起点
    for(int j=i;j<n;j){  //去掉的终点
        string t=s.substr(0,i)+s.substr(j+1)
            if(t=="2021"){
                ok=true
            }
    }  
//优化后代码
bool ok=false;
for(int i=0;i<=4;i++){
    string t=s.substr(0,i)+s.substr(n-(4-i))
        if(t=="2021"){
            ok=true
        }
}
校园活动(之后再编)

题目链接

递推递归

常用于序列计算,序列中的每一项依赖于前面一个或多个项的值

递归更像是一种编程技巧,代码会更加简洁

一个函数调用自己即是递归

递归递推差异
  1. 二者对问题求解方式本质上是一样的,不同的是求解次序。
  2. 从前往后是递推,从后往前求是递归。
  3. 有已知到未知是递推,由未知到已知是递归。
阶乘

计算某数的阶乘

最简单的算法题.定义一个long long类型的数组

long long fac[n+1]{}; //long long fac[n+1]={};

然后直接递推,得出结果

递归解法如下:

long long fac(int n){
    if(n==0){
        return 1;
    }
    return n*fac(n-1);
}
int main(void){
    int n;
    cin<<n;
    return 0;
}

//优化一下
long long fac(int n){
    return n==0?1:n*fac(n-1);
}
兔子兔子

斐波那契数列

F n = F n − 1 + F n − 2 , n > = 2 F_n=F_{n-1} + F_ {n-2},n>=2 Fn=Fn1+Fn2,n>=2

// 递推写法
int main(void){
    int n;
    cin>>n;
    long long f[n+1]{};
    f[1]=f[2]=1;
    for(int i=3;i<=n;i++){
        f[i]=f[i-1]+f[i-2];
    }    
    cout<<f[n]<<"\n";
    return 0;
}
//递归写法
long long f(int n){ //返回第n月有多少兔子
    if(n==1orn==2){
        return 1;
    }
    return f(n-1)+f(n-2);
 }

int main(void){
    int n;
    cin>>n;
    cout<<f(n)<<"\n";
    return 0;
}

碎梦

题干:窗台上有 n堆纸飞机数目分别为 1 , 2 , … , n 。每次可以选择一个数 x,然后从所有纸飞机数目大于等于x的堆中掷出x只纸飞机.问最少要选择多少次才能将所有纸飞机掷出?

递归最重要的是终止条件和递归函数体

//取中值开涮
/*
    n=5
    1 2 3 4 5
    x=1; 0 1 2 3 4
    x=2; 1 0 1 2 3
    x=3; 1 2 0 1 2
    x=4; 1 2 3 0 1
    x=5; 1 2 3 4 0
    
    
    n=6
    1 2 3 4 5 6 
    x=1; 0 1 2 3 4 5 
    x=2; 1 0 1 2 3 4
    x=3; 1 2 0 1 2 3 
    x=4; 1 2 3 0 1 2 
    x=5; 1 2 3 4 0 1 
    x=6; 1 2 3 4 5 0
    */
#include<bits/stdc++.h>
using namespace std;

int solve(int n){
    if(n==1){
        return 1;  //终止条件
    }
    if(n%2==1){
        return 1+solve((n-1)/2);//(n-1)/2==n/2
    }else{
        r#include<bits/stdc++.h>
using namespace std;

int solve(int n){
    if(n==1){
        return 1;  //终止条件
    }
    if(n%2==1){
        return 1+solve((n-1)/2);//(n-1)/2==n/2
    }else{
        return 1+solve(n/2);
    }
}
int main(void){
    int n;
    cin>>n;
    cout<<solve(n)<<"\n";
    return 0;
}
//优化
if(n==1){   //solve(n)=1+solve(n/2)
            //f[n]=1+f[n/2] 递推代码
    return 1;
}return 1+solve(n/2);
//再优化
return n==1?1:1+solve(n/2);
(n-1)/2与n/2

在n为奇数条件下,两者相同;若为偶,则不相同

二分

引子

给n个数,m次询问,每次询问给一个数a,找到n个数中比a小的最大的数,数据保证这样的数存在。

把n个数排序,把此数组依照从小到大的顺序遍历,找到小的最大的数。此种算法每次询问时遍历一遍原数组,时间复杂度 O(n*m).

离线

把n次询问先进行操作,即先把数组排序,查询值也排序,遍历一遍,可一一对应。 时间复杂度O(nlog n)

STL
//运用set
set<int>s;
s.lower_bound(a);
s.lower_bound(a--);
方法

我们先将数组进行排序,对于每次询问假设答案的下标在k,那么k之前的所有数必定满足其小于a。K之后的所有数必定满足k大于等于a。 我们把满足 小于a 这个作为条件,如果满足,就定义其为合法(1),不满足就定义其为非法(0),那么序列应该是这个样子

111……111000……000

我们的目的就是找到最大的1所在的位置。

//写一个check函数
bool check(int id){
    if(w[id]<a) return 1;
    return 0;
}
//当原数组n过大时,通过枚举找最后一个1的过程非常慢,是否有简便方法?

简单来说,我们维护一个区间使答案必定在这个区间内,然后我们继续维护,使得左端点必定合法(check为1),右端点必定不合法(check为0)然后不断通过取左右端点的中点的方式来每次将这个区间缩短一半,这样当ls+1=rs的时候,左端点就是答案。

其实就是一个左闭右开的区间。

bool check(int id){     //贪心扫描
     if(w[id]<a) return 1;
     return 0;
}
void work1(){
    int ls=1,rs=n+1; //左闭右开,保证左端点永远合法
    while(ls+1<rs){
        in mid=(ls+rs)/2;
        if(!check(mid))   rs=mid;
        else ls=mid;
    }
    printf("%d",w[ls]);
}

简单来说,如果我们已经实现了check函数,那么无非是下列两种情况

11111110000000 让你求最大的1的位置

00000001111111 让你求最小的1的位置

第一个就是上述方法(左闭右开)

第二个只需要维护ls永远非法,rs永远合法即可(左开右闭)。

二分的难点其实在于check函数的实现,即如何判断当前位置的数是否满足题目要求

NOIP2015 跳石头

(终于开始写题了!)

数轴上有n个石子,第i个石头的坐标为Di,现在要从0跳到L,每次跳都从一个石子跳到相邻的下一个石子。现在FJ允许你最多移走M个石子,问移走这M个石子后,相邻两个石子距离的最小值最大是多少。(N<=50000,L<=1e9)

/*10
1 2 3 4 5 6 7 8 9 10(to be honest 算法竞赛上我最大的石头反倒是阅读理解题目含义,而非算法,这就是思维流化的坏处吗) */

“最小值最大”二分算法经典标志

//移走M即为移走 两个相隔间的最小,使其在不断移走中不断变大
//如果a对应的M合法,a-1对应的M更小,因此必定合法,此a-1不是说两数间距离此时为a-1,而
//是当最小距离为a-1合法。如果把a-1理解为此时某两数间距离,则不合法,需要进行移动
#include<bits/stdc++.h>
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;
    // 以上源代码来自洛谷ShawnZhou题解
}
  1. 如果一个距离满足题意,则更小的距离肯定满足,所以答案满足单调性,考虑二分答案。

  2. 那么我们在跳跃距离[0,L]之间二分枚举一个最小跳跃距离作为答案mid进行判断,看是否符合最多移走m个的限制,如果符合,那么mid可能就是答案,但也可能还存在更大的答案,所以要向右二分查找。如果不行,那肯定大了,向左二分查找。

  3. 判定直接O(N)的贪心即可

  4. 总时间复杂度为NlogL

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值