算法笔记刷题记录

算法笔记做题记录

第三章 入门篇

3.1简单模拟

B1001

B1011. A+B和C
由于数据范围是2^31,而int型的数据最大可以表示到此。但是A+B之后可能会爆int,所以定义 long long。scanf的时候这么写scanf(“%lld%lld%lld”,&a,&b,&c);

B1016. 部分A+B
也是long long的问题,也可以考虑用高精度算法做

B1026. 程序运行时间
这是一个格式模拟的基本问题,有个比较特殊的地方在于其四舍五入的操作。本题中:

两次获得的时钟打点数之差 (C2-C1) 就是 f 运行所消耗的时钟打点数,再除以常数 CLK_TCK,就得到了以秒为单位的运行时间。

因为要除以100,本题根据C2-C1的末两位是否大于50来决定是进还是舍。

if((c2-c1)%100/50) time = (c2-c1)/100+1;
else time = (c2-c1)/100;

B1046. 划拳

B1008. 数组元素循环右移问题
需要注意,移动的位数可能是大于N的,需要先对m取个模。同时,对于尾巴移出去的部分,这题采用了一个比较取巧的方法,因为不能开额外的数组,所以直接将他们先输出。

B1012. 数字分类
①保留一位小数的输出方法

if(c4!=0) printf("%.1f ",(double)a4/count);

②本题a1,…a5是要输出的数字,不代表该分类下的数目。不是把a1…a5的值作为判断数量的依据。导致一开始有一个测试点没有AC

B

第四章 算法初步

4.1 排序

选择排序

void selectsort(){
    for(int i=0;i<n;i++){//总共进行n趟排序
        int k=i;//每次从当前位置往后的作为待排序项
        for(int j=i;j<n;j++){
            if(A[j]<A[k]) k=j;//记录最小的数的下标信息
        }
        int tmp=A[i];
        A[i] = A[k];
        A[k] = tmp;
    }
}

插入排序

void insersort(){
    for(int i=2;i<=n;i++){//下标从1开始的数组
        int tmp=A[i];//临时存放A[i]
        j=i;//j从i开始往前枚举
        while(j>1 && tmp<A[j-1]){
            A[j] = A[j-1];//向后移
            j--;
        }
        A[j] = tmp;
    }
}

直接调用c++的库函数sort

如何使用? 必须加上头文件

"#include < algorithm> "

sort函数有三个参数,分别是首元素的地址,尾元素地址的下一个地址,比较函数(非必要)。如果不写比较函数的话,则默认对前面给出的区间进行递增的排序。比如要进行a[0]~a[3]的排序,要这么写sort(a,a+4)。
可以对double,char做排序。顺便看到一个输出char的printf,printf(“%c”,c[i]). 注意:对char型数组的排序默认是按照字典序

由于需要对序列进行排序的前提是序列元素可比,像结构体本身并没有大小关系,需要人为制定排序规则,这时候sort的第三个参数就起作用了。第三个参数是compare函数cmp。下面总结对于基本数据类型、结构体类型、STL容器的cmp写法。

(1)基本数据类型
若不指定cmp,默认是对int数组进行升序排列。但是如果想递减的话,要用cmp告诉sort何时交换元素。

bool cmp(int a,int b){
	return a>b;//理解为a>b时把a放在b的前面
}

double,char同样如此

(2)结构体类型

#include<bits/stdc++.h>
using namespace std;

struct node{
    int x,y;
}ssd[10];

bool cmp(node a ,node b){
    return a.x>b.x;//从大到小排序
}

int main(){
    ssd[0].x = 2;
    ssd[0].y = 2;
    ssd[1].x = 1;
    ssd[1].y = 3;
    sort(ssd,ssd+2,cmp);
}

若想先按照X的大小排序,y按照从小到大的话,只需要这样实现:

bool cmp(node a,node b){
	if(a.x!=b.x) return a.x>b.x;
	else return a.y<b.y;
}

(3)容器的排序
在STL的标准容器中,只有vector,string,deque是可以用sort的

#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
bool cmp(int a,int b){//因为vector中的元素是int型,因此仍然是int类的比较.一定要注意这里传值传的是int
    return a>b;
}
int main(){
    vector<int> vi;
    vi.push_back(3);
    vi.push_back(1);
    vi.push_back(2);
    sort(vi.begin(),vi.end(),cmp);//对整个vector排序
    for(int i=0;i<3;i++){
        printf("%d ",vi[i]);
    }
    return 0;
}

对于string的排序,默认是按照字典序排。比如下面的代码如果不加cmp函数,输出是aaa bbbb cc:

#include<stdio.h>
#include<string>
#include<algorithm>
using namespace std;
bool cmp(string a,string b){//按照字符串的长度排序
    return a.length()<b.length;
}
int main(){
    string str[3]={"bbbb","cc","aaa"};
    sort(str,str+3,cmp);
    for(int i=0;i<3;i++){
        cout<<str[i]<<" ";
    }
    return 0;
}

但是加了cmp函数之后输出就是cc aaa bbbb

解题技巧

由于排序题往往会在一个题目中给出个体的许多信息。这些信息在排序的时候都用的到。常见的操作是用结构体进行存储
在书中提到了一种题目的问法:对所有学生先进行分数从高到低排序,分数相同的按姓名字典序升序。其cmp函数实现为:

bool cmp(Student a,Student b){
    if(a.sc!=b.sc) return a.ac>b.sc;
    else return strcmp(a.name,b.name)<0;
}

这里要注意,由于不同的编译器对于strcmp的返回值不同,所以直接==-1是不对的。同时,a.name若小于b.name,strcmp函数返回的是负数其实就是-

排名的实现
大多数题目会要求排序分数之后输出一个排名,且要求排名相同的占用一个排位,不同的不同排位。解决这类问题,技巧就是直接在结构体定义的时候就把排名加到结构体中。接下来,基于排序后的结果,先把数组的第一个个体排名记成1,遍历剩下的个体,如果当前遍历到的个体分数等于上一个个体的分数,当前排名等于上个人的排名,否则排名等于数组下标+1。代码如下:

stu[0].r=1;
for(int i=1;i<n;i++){
    if(stu[i]==stu[i-1]) stu[i].r = stu[i-1].r;
    else stu[i].r = i+1;
}

A1025 PAT Ranking

利用了上述的解题技巧,并且用了sort函数。当时卡着没AC因为cmp函数的比较写成了当学号不等的时候排序,但是学号不可能不等,应该是分数不等的时候从大到小排。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

struct node{
    char num[15];
    int score;
    int room;
    int r;
    int localrk;
}stu[30010];

bool cmp(node a,node b){
    if(a.score!=b.score) return a.score>b.score;
    else return strcmp(a.num,b.num)<0;
}

int main(){
    int x;
    cin>>x;
    int count=0;
    for(int i=0;i<x;i++){
        int n;
        cin>>n;
        int rm = i;//房间号从0开始,注意要加一
        for(int j=0;j<n;j++){
            cin>>stu[count].num>>stu[count].score;
            stu[count].room = rm;
            count++;
        }
        sort(stu+count-n,stu+count,cmp);
        stu[count-n].localrk = 1;
        for(int i=count-n+1;i<count;i++){
            if(stu[i].score==stu[i-1].score) stu[i].localrk = stu[i-1].localrk;
            else stu[i].localrk=i+1-(count-n);
        }
    }
    
    sort(stu,stu+count,cmp);
    stu[0].r = 1;
    for(int i=1;i<count;i++){
        if(stu[i].score==stu[i-1].score) stu[i].r = stu[i-1].r;
        else stu[i].r=i+1;
    }
    cout<<count<<endl;
    for(int i=0;i<count;i++){
        cout<<stu[i].num<<" "<<stu[i].r<<" "<<stu[i].room+1<<" "<<stu[i].localrk<<endl;
    }
    
}

A.1012 The Best Rank

这题写了有点久,对于结构体里的元素的定义事先考虑的不好,导致后面一直修改。其次,读入char数组比较,初始化的query char数组一定是要写成数组形式,不然就会指针异常。然后char组的比较用strcmp看是否为0,0就表示相等。
这题代码可以优化,因为id号短,可以直接用id号作为数组下标,用int存储不用char,然后类似哈希的思想直接映射就可以对应数组下标了。就不用多一层for循环去找对应的id。

#include<bits/stdc++.h>
using namespace std;

int now=0;
struct node{
    char id[8];
    int sc[4];
    int flag;
    int r[4];
}stu[2010];

bool cmp(node a,node b){
    return a.sc[now]>b.sc[now];
}

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>stu[i].id>>stu[i].sc[1]>>stu[i].sc[2]>>stu[i].sc[3];
        
        stu[i].sc[0] = (stu[i].sc[1]+stu[i].sc[2]+stu[i].sc[3])/3;
    }
    for(now;now<4;now++){
    sort(stu,stu+n,cmp);
    stu[0].r[now] = 1;
    for(int i=1;i<n;i++){
       if(stu[i].sc[now]==stu[i-1].sc[now]) {
                stu[i].r[now]=stu[i-1].r[now];
                
            }else{
                stu[i].r[now] = i+1;
            }
        }   
    }
    
    for(int i=0;i<m;i++){
        char query[6];
        cin>>query;
        int loc=-1;
        for(int p=0;p<n;p++){
            if(strcmp(stu[p].id,query)==0) loc = p;
        }
        if(loc==-1) cout<<"N/A"<<endl;
        else{
            int min=stu[loc].r[0];
            int f=0;
            for(int j=1;j<4;j++){
            if(stu[loc].r[j]<min){
                min = stu[loc].r[j];
                f = j;
            }
            }
        if(f==0) cout<<min<<" "<<"A"<<endl;
        if(f==1) cout<<min<<" "<<"C"<<endl;
        if(f==2) cout<<min<<" "<<"M"<<endl;
        if(f==3) cout<<min<<" "<<"E"<<endl;
        }
    }
}


A.1016 Phone Bills

第一个做的比较长的代码题,逻辑比较复杂,先过,倒回来看看

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1010;
int toll[25];//用来记录每个月的资费

struct Record{
    char name[25];
    int month,dd,hh,mm;
    bool status; //true记录为online否则offline
}rec[maxn],temp;

bool cmp(Record a,Record b){
    int s = strcmp(a.name,b.name);
    if(s!=0) return s<0;
    else if(a.month!=b.month) return a.month<b.month;
    else if(a.dd!=b.dd) return a.dd<b.dd;
    else if(a.hh!=b.hh) return a.hh<b.hh;
    else return a.mm<b.mm;
}

void get_ans(int on,int off, int& time,int& money){
    temp = rec[on];
    while(temp.dd<rec[off].dd||temp.hh<rec[off].hh||temp.mm<rec[off].mm){
        time++;
        money+=toll[temp.hh];
        temp.mm++;
        if(temp.mm>=60){
            temp.mm=0; //进入下一个小时
            temp.hh++;
        }
        if(temp.hh>=24){
            temp.hh = 0;
            temp.dd++;
        }
    }
}

int main(){
    for(int i=0;i<24;i++){
        scanf("%d",&toll[i]);
    }
    int n;
    scanf("%d",&n); //记录数
    char line[10]; //存放online offline字符串
    for(int i=0;i<n;i++){
        scanf("%s",rec[i].name);
        scanf("%d:%d:%d:%d",&rec[i].month,&rec[i].dd,&rec[i].hh,&rec[i].mm);
        scanf("%s",line);
        if(strcmp(line,"on-line")==0) rec[i].status = true;
        else rec[i].status = false;
    }
    sort(rec,rec+n,cmp); //排序
    int on = 0, off,next; //on和off是匹配的两个记录,Next是下一个用户
    while(on < n){
        int needprint = 0; //表示是否需要输出
        next  = on;
        while(next<n && strcmp(rec[next].name,rec[on].name)==0){
            if(needprint==0 && rec[next].status == true) needprint =1;
            else if(needprint==1 && rec[next].status == false) needprint =2;
            next++;//next自增,直到找不到同名的
        }
        
        if(needprint<2) {
            on = next;//说明无匹配
            continue;
        }
        int allmoney = 0;
        printf("%s %02d\n",rec[on].name,rec[on].month);
        while(on<next){
            while(on<next-1 && !(rec[on].status == true && rec[on+1].status==false)){
                on++;
            }
            off = on+1;
            if(off == next){//已经输出完毕所有的配对了
                on = next;
                break;
            }
            printf("%02d:%02d:%02d ",rec[on].dd,rec[on].hh,rec[on].mm);
            printf("%02d:%02d:%02d ",rec[off].dd,rec[off].hh,rec[off].mm);
            int time = 0,money=0;
            get_ans(on,off,time,money);//计算on到off之间花费的钱
            allmoney+= money;
            printf("%d $%.2f\n",time,money/100.0);
            on = off+1;//完成了一组配对,从off+1开始下一对
        }
        printf("Total amount: $%.2f\n",allmoney/100.0);
    }
    return 0;
}

输出样例:

CYJJ 01
01:05:59 01:07:00 61 $12.10
Total amount: $12.10
CYLL 01
01:06:01 01:08:03 122 $24.40
28:15:41 28:16:05 24 $3.85
Total amount: $28.25
aaa 01
02:00:01 04:23:59 4318 $638.80
Total amount: $638.80

做到这里发现,关键在于cmp函数的实现。大体会出现这些状况:结构体里的不同属性字段有优先级差别进行排序;分开不同情况对不同属性排序。 前者可以用数组存储不同属性,设置全局变量放到循环中迭代,根据该变量的不同取值对对应数组的分量进行排序。而后者只需要设计多个cmp函数即可。有一类题目比较特殊,就是要给定查询条件的,可以用哈希的思想快速映射不用再查找一遍了。

A.1028 List Sorting

这题简单的分几个cmp函数即可,不贴代码了

A.1055 The World’s Richest

对cmp函数的简单变形,简单题

A.1075 PAT Judge

很奇怪还有几个测试点没过,算是比较长的较难题了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
struct node{
    int id;//考号
    int r;//排名
    int tr;//尝试提交次数。只要交了就是有记录
    int xtf[6];//小题分数
    int xtt[6];//小题提交次数
    int score;//总分
    int perfect;//完美解决数量
    int jilu;
}stu[N];

bool cmp(node a,node b){
    if(a.score!=b.score) return a.score>b.score;
    else if(a.perfect!=b.perfect) return a.perfect>b.perfect;
    else return a.id<b.id;
}


int main(){
    int n,m,s;
    cin>>n>>m>>s;
    int sc[m];//记录每道题的分数
    for(int i=1;i<=m;i++){
        cin>>sc[i];
    }
    int sid,fid,kid;
    for(int i=0;i<s;i++){
        scanf("%d%d%d",&sid,&fid,&kid);
        stu[sid].id = sid;
        stu[sid].xtt[fid]++;
        stu[sid].tr++;
        if(kid>=0) {
            if(kid>stu[sid].xtf[fid]) {
                stu[sid].score+=(kid-stu[sid].xtf[fid]);
                stu[sid].xtf[fid] = kid;
            }
            stu[sid].jilu++;//编译通过
        }
        if(kid==sc[fid]) stu[sid].perfect++;
    }
    sort(stu+1,stu+n+1,cmp);
    stu[1].r=1;
    for(int i=2;i<=n;i++){
        if(stu[i].score==stu[i-1].score) stu[i].r=stu[i-1].r;
        else stu[i].r = i;
    }
    
    for(int i=1;i<=n;i++){
        if(stu[i].jilu==0||stu[i].tr==0) continue;//表示没有能够通过编译的提交
        cout<<stu[i].r<<" 0000"<<stu[i].id<<" "<<stu[i].score;
        for(int j=1;j<=m;j++){
            if(stu[i].xtt[j]==0) cout<<" -";
            if(stu[i].xtt[j]>0) cout<<" "<<stu[i].xtf[j];
            if(j==m) cout<<endl;
        }
    }
}

4.2 散列

A.1084 Broken Keyboard

由于忽略大小写所以要同时将大小写的字母转成大写,这里减去32。遍历正确的串的每一位,键盘实际的从前向后遍历找有没有相同的,如果找到相同的退出。如果到了结尾还没有,查哈希表,如果之前没输出过,输出,并改成true;这里注意内层的循环变量要在循环前声明。

#include<bits/stdc++.h>
using namespace std;

int main(){
    string str1,str2;
    bool HashTable[128] = {false};
    cin>>str1>>str2;
    int len1 = str1.length();//第一个字符串的长度
    int len2 = str2.length();
    for(int i=0;i<len1;i++){//枚举第一个字符串中的每个字符
        int j;
        char c1,c2;
        for(j=0;j<len2;j++){//这里的j不是临时的,后面要用
            c1 = str1[i];
            c2 = str2[j];
            if(c1>='a' && c1<='z') c1-=32;//小写转成大写
            if(c2>='a' && c2<='z') c2-=32;//小写转大写
            
            if(c1==c2) break;
        }
        if(j == len2 && HashTable[c1] == false) {
            //说明没输出过
            printf("%c",c1);
            HashTable[c1] = true;
        }
    }
    return 0;
}

B.1033 旧键盘打字

震惊,PTA居然不支持gets,搞了好久我还以为写错了。 在PTA平台里gets要用cin.getline(变量,长度);其他的逻辑挺简单的。还有个memset是初始化一个数组的值.
总体思路就是,先把坏掉的读进来存起来,大写转成小写;要输出的读进来,大写转小写,如果是大写的话,判断hashtable里的是坏了还是没坏并且加上+的判断;如果是小写,就看hashtable让不让输出,让的话直接输出。

#include<bits/stdc++.h>
#include<cstring>
using namespace std;
const int maxn = 1e5+10;
bool hashtable[256];
char str[maxn];

int main(){
    memset(hashtable,true,sizeof(hashtable));
    cin.getline(str,maxn);
    int len = strlen(str);
    for(int i=0;i<len;i++){
        if(str[i]>='A'&&str[i]<='Z'){
            str[i] = str[i]-'A'+'a';
        }
        hashtable[str[i]] = false;
    }
    cin.getline(str,maxn);
    len = strlen(str);
    for(int i=0;i<len;i++){
        if(str[i]>='A'&&str[i]<='Z'){
            int low = str[i]-'A'+'a';
            if(hashtable[low]==true&&hashtable['+']==true){
                cout<<str[i];
            }
        }else if(hashtable[str[i]]==true){
            cout<<str[i];
        }
    }
    cout<<endl;
}

B.1038 统计同成绩学生

这题太水了不记录了

B.1039 到底买不买

输入串的长度统计用strlen统计,不是用sizeof,这样会多出来。另外对于hash函数的定义,当题目有多个数组需要映射的时候,且映射规则一样,可以抽出来当一个函数。

#include<bits/stdc++.h>
using namespace std;

const int maxn=1010;
int hashtable[80]={0},miss = 0;

int change(char c){
    if(c>='0' && c<='9') return c-'0';
    if(c>='a' && c<='z') return c-'a'+10;//前面有10个数字
    if(c>='A' && c<='Z') return c-'A'+36;//前面的数字和字母加起来36个
}

int main(){
    char whole[maxn],target[maxn];
    cin.getline(whole,maxn);
    cin.getline(target,maxn);
    int len1 = strlen(whole);
    int len2 = strlen(target);
    
    for(int i=0;i<len1;i++){
        int id = change(whole[i]);
        hashtable[id]++;
    }
    
    for(int i=0;i<len2;i++){
        int id = change(target[i]);
        hashtable[id]--;
        if(hashtable[id]<0) miss++;
    }
    if(miss>0) cout<<"No "<<miss;
    else cout<<"Yes "<<len1-len2;
}

B.1042 字符统计

if(query[i]>='A'&&query[i]<='Z') {
            query[i] = query[i]-'A';//由于不区分大小写,这样就可以统一映射了,不用加+‘a’
            ht[query[i]]++;
        }else if(query[i]>='a'&&query[i]<='z'){
            ht[query[i]-'a']++;
        }
 ...
 cout<<(char)('a'+k)<<" "<<max;//这个地方要用(char)转一下不然显示不出字符       

B.1043 输出PATest

这题比较简单一遍AC不记录了

B.1047 编程团体赛

这题就注意输入用scanf就可以

for(int i=0;i<n;i++){
        scanf("%d-%d %d",&team,&id,&sc);
        ht[team] = ht[team]+sc;
    }

A.1041 Be Unique

不用开两个数组记录时间戳和hash。直接开一个输入存储数组然后从前向后遍历就是输入的顺序了。这个题在PAT里现在time limit开到了200ms,原来100ms cin输入会超时。

A.1050 String Subtraction

①书上说的注意点:在for循环的时候是用i<strlen(str)是个很不好的行为。因为本身strlen这个函数就是从前向后扫描字符串,他的时间复杂度是O(N),放在循环里会增加一次指数的复杂度。好的做法就是用int len去接收长度变量
②读入字符串的时候可以用gets函数一次直接读入一整行,也可以用getchar函数一次读入一个字符,读到‘\n’为止。写法如下:

char s[max],a;
int lena=0;
while(true){
	s[lena++] = getchar();
	if(s[lena-1]=='\n' break;//必须有\n作为字符串的结束字符
}

除此之外不建议使用scanf读取需要一次读取一整行的字符串的情况,因为他是看空格作为截断的。

4.3 递归

4.3.1分治

4.3.2递归

这一节在实战书上没有对应的习题。先跳过后面回来做

4.4 贪心

推导矛盾。

4.4.1简单贪心

下面这题就是一个典型题,按单价贪心。结合了之前的排序专题,实现cmp函数先对单价排序,使用结构体定义月饼。计算总价的时候进行一个判断,如果剩余需求量大于库存量,直接需求量减掉库存量,总收入加上该月饼的总售;如果不够了,就单价*剩下的需求

B.1020 月饼

#include<bits/stdc++.h>
using namespace std;

struct mooncake{
    double store;//库存量
    double sell;//总售价
    double price;//单价
}cake[1010];

bool cmp(mooncake a,mooncake b){
    return a.price>b.price;
}

int main(){
    int n;
    double D;
    scanf("%d%lf",&n,&D);
    for(int i=0;i<n;i++){
        scanf("%lf",&cake[i].store);
    }
    for(int i=0;i<n;i++){
        scanf("%lf",&cake[i].sell);
        cake[i].price = cake[i].sell/cake[i].store;//求出单价
    }
    sort(cake,cake+n,cmp);
    double ans=0;
    for(int i=0;i<n;i++){
        if(cake[i].store<=D){
            D-=cake[i].store;
            ans+=cake[i].sell;
        }else{
            ans+=cake[i].price*D;
            break;
        }
    }
    printf("%.2f",ans);
}

B.1023 组个最小数

这题比较水

4.4.2 区间贪心

#include<bits/stdc++.h>
using namespace std;

const int maxn=110;

struct Interval{
    int x,y;//开区间的左右端点
}I[maxn];

bool cmp(Interval a,Interval b){
    if(a.x!=b.x) return a.x>b.x;//左端点从大到小排序
    else return a.y<b.y;//左端点相同的按照右端点从小到大排序
}

int main(){
    int n;
    while(scanf("%d",&n),n!=0){
        for(int i=0;i<n;i++){
            scanf("%d%d",&I[i].x,&I[i].y);
        }
        sort(I,I+n,cmp);
        int ans=1,lastx = I[0].x;
        for(int i=1;i<n;i++){
            if(I[i].y<=lastx){
                lastx = I[i].x; //以新的区间的左端点为新的点
                ans++; //不相交的区间数加一
            }
        }
        printf("%d\n",ans);
    }
}

实战书:

A.1033 To Fill or Not to Fill

此题比较难,思路基本都在注释里写了,晚上自己再整理一遍思路

#include<bits/stdc++.h>
using namespace std;
const int maxn = 510;
const int INF = 1000000000;

struct station{
    double price,dis;
}st[maxn];

bool cmp(station a,station b){
    return a.dis<b.dis; //按照距离的大小排序
}

int main(){
    int n;
    double Cmax,D,Davg;
    scanf("%lf%lf%lf%d",&Cmax,&D,&Davg,&n);
    for(int i=0;i<n;i++){
        scanf("%lf%lf",&st[i].price,&st[i].dis);
    }
    st[n].price = 0; //设置重点的价格是0
    st[n].dis = D; //距离是最远距离
    
    sort(st,st+n,cmp);//按照距离从小到大排序
    
    if(st[0].dis!=0) printf("The maximum travel distance = 0.00\n");
    else{
        int now = 0;//现在的加油站编号
        //定义为总花费、当前油量、满油行驶距离
        double ans = 0,nowtank = 0,MAX = Cmax*Davg;
        while(now<n){//每次循环将选出下一个要到达的加油站
            int k = -1;//最低油价的加油站编号
            double pricemin = INF;
            for(int i=now+1;i<=n&&st[i].dis-st[now].dis<=MAX;i++){
                //遍历找比当前高的油价里的最低价
                if(st[i].price<pricemin){
                    pricemin = st[i].price;
                    k = i;
                    if(pricemin<st[now].price) break;//找到了一个比当前油价低的停止遍历
                }
            }
            if(k==-1) break; //满油情况下都找不到可以到的,退出循环
            //为计算从now到k加油站需要的油量
            double need = (st[k].dis - st[now].dis)/Davg;
            if(pricemin < st[now].price){//如果加油站k的油价低于当前的油价
                //只需要买足够到达加油站k的油
                if(nowtank < need){
                    //只需要加到够到下一个站即可
                    ans+=(need-nowtank)*st[now].price;
                    nowtank = 0;//到达下一个站之后油量0
                }
                else{
                    nowtank -= need;
                }
            }else{//如加油站k油价高于当前
                ans+=(Cmax-nowtank)*st[now].price;//当前的注满
                nowtank = Cmax-need;//到达下一个站之后剩下的
            }
            now = k;
        }
        if(now == n) printf("%.2f\n",ans);
        else printf("The maximum travel distance = %.2f\n",st[now].dis+MAX);
    }
}

A.1037 Magic Coupon

只需要将数组排个序就行了,对于负数,从左向右遍历,到出现非负为止;对于正数,从数组的右端向左遍历到非正停止。排序之后的贪心策略。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int q[N],p[N];


int main(){
    int n,m;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>q[i];
    }
    cin>>m;
    for(int i=0;i<m;i++){
        cin>>p[i];
    }
    sort(q,q+n);//从小到大排序,先是负数
    sort(p,p+m);
    int ans = 0;
    for(int i=0;i<m&&i<n;i++){
        if((q[i]*p[i]<=0)||(q[i]>0&&p[i]>0)) break;
        else{
            ans+=q[i]*p[i];
        }
        
    }
    for(int i=n-1,j=m-1;i>=0&&j>=0;i--,j--){
        if((q[i]*p[j]<=0)||(q[i]<0&&p[j]<0)) break;
        else{
            ans+=q[i]*p[j];
        }
    }
    cout<<ans;
}

A.1067 Sort with Swap(0, i)

贪心选择:每次选择当前0所在的下标对应的数和他交换
难点:当0回到0的时候,算法会终止。这时候需要遍历从小到大找第一个不在本位上的进行交换。这时候要注意Left记录的不在本位上的个数是不会发生变化的。这里用了哈希表的存储思想。

#include<bits/stdc++.h>
using namespace std;
const int N = 1E5+10;
int pos[N];
int main(){
    int n,ans=0;
    cin>>n;
    
    int left = n-1,num;//left存放除了0之外不在本位上的个数
    for(int i=0;i<n;i++){
        cin>>num;
        pos[num] = i;
        if(num==i&&num!=0) left--;
    }
    int k = 1;//存放除了0之外不在本位上的最小的数
    while(left>0){
         //如果0在本位上,寻找一个当前不在本位上的与0交换
        if(pos[0]==0){
            while(k<n){
                if(pos[k]!=k){
                    swap(pos[0],pos[k]);
                    ans++;
                    break;
                }
                k++;
            }
        }
        //只要0不在本位
        while(pos[0]!=0){
            swap(pos[0],pos[pos[0]]);
            ans++;
            left--;
        }
        
        
    }
    cout<<ans;
}

4.5 二分

4.5.1 二分查找

问题背景是在一个有序(以严格递增为例)序列中找出给定的数x。 基础的思想是顺序遍历一遍,时间复杂度为O(N).于是优化采用二分查找,复杂度O(logN)。
对整数二分问题和浮点数二分,采用Acwing提供的模板;

//整数二分
int n,m;
int q[N];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    while(m--){
        int x;
        scanf("%d",&x);
        int l=0,r=n-1;
        while(l<r){
            int mid=l+r>>1;
            if(q[mid]>=x) r=mid;
            else l=mid+1;
        }
        if(q[l] !=x) cout<<"-1 -1"<<endl;
        else{
            cout<<l<<"";
            int l=0,r=n-1;
            while(l<r){
                int mid=l+r+1>>1;
                if(q[mid]<=x) l=mid;
                else r=mid-1;
            }
            cout<<l<<endl;
        }       
    }
}

另一种模板是l=mid的,这时候的初始化的mid需要l+r+1>>1.

int l=0,r=n-1;
while(l<r){
    int mid=l+r+1>>1;
    if(q[mid]>=x) l=mid;
    else r=mid-1;
    }

这两个模板的区别主要看更新的时候是怎么更新的。如果每次更新的r=mid的话,就第一种不+1;如果l=mid的话,就要把起始的mid+1

4.5.2 二分法拓展

比如求根号2的近似值,因为在1到2,x方是单增的,利用这个单调的性质,可以使用二分法求解。这时候是浮点数二分,浮点数二分比整数二分简单多了。这里以10^-5作为精度。代码如下:

const double eps = 1e-5;

double f(double x){
    return x*x;
}

double calSqrt(){
    double left = 1,right = 2,mid;
    while(right-left>eps){
        mid =(left+right)/2;
        if(f(mid)>2){
            right = mid;
        }else{
            left = mid;
        }
    }

    return mid;
}

4.5.3 快速幂

基本思想是求幂的时候不要一遍一遍乘底数,直接用2倍接近目标。先给出代码,再提示几个需要注意点
求解a^b % m

typedef long long LL;

LL binaryPow(LL a,LL b,LL m){
    if(b==0) return 1;
    //如果b是奇数,那么拆出一个底数
    if(b&1) return a*binaryPow(a,b-1,m)%m;
    else{
        LL mul = binaryPow(a,b/2,m);
        return mul*mul%m;
    }
}

注意点一:判断b奇数用了b&1,这是按位与,判断末位是不是1
注意点二:递归偶数的时候,用一个临时变量mul存了结果,而不是直接return binaryPow(a,b/2,m)*binaryPow(a,b/2,m)%m,因为这样会导致多次递归,退化了时间复杂度。这个思想并行计算可以用来优化!

题单

A.1085 Perfect Sequence

为了利用二分,需要把数组构建成单调数组。因此首先先对数组进行排序,之后问题就转化为,给定一个区间的左端点找满足条件的最右边。如果套两层for会超时,因此用到了二分技术。对于每个数q[i],向右找到最大的q[j],计算下标差值。
注意点:
①p和元素都可能到达10^9,因此相乘结果可能爆int,要用long long转换
②二分查找可以用upper_bound函数代替

#include<bits/stdc++.h>
using namespace std;
const int N = 1E5+10;
int q[N];


int main(){
    int n,p;
    scanf("%d%d",&n,&p);
    for(int i=1;i<=n;i++){
        scanf("%d",&q[i]);
    }
    sort(q+1,q+n+1);
    int res=1,l,r=n;
    for(int i=1;i<=n;i++){
        int j = upper_bound(q+i+1,q+n+1,(long long)q[i]*p) - q;
        res = max(res,j-i);
    }
    cout<<res;
}

该死的二分,真的蛋疼。被折磨了一个小时…手写二分就是各种出问题:
注意这里必须是r-i+1,因为l-i+1的话,因为l始终比i后一个,也就是l-i+1至少2这就错了,在长度为1的时候会报错。而r则不会。

#include<bits/stdc++.h>
using namespace std;
const int N = 1E5+10;
int q[N];


int main(){
    int n,p;
    scanf("%d%d",&n,&p);
    for(int i=0;i<n;i++){
        scanf("%d",&q[i]);
    }
    sort(q,q+n);
    int res=1,l,r;
    for(int i=0;i<n;i++){
        l=i+1;
        r=n-1;
        while(l<r){
            int mid = (l+r+1)>>1;
            if(q[mid]<=(long long)q[i]*p) l=mid;
            else r=mid-1;
        }
        res = max(res,r-i+1);
    }
    cout<<res;
}

A 1010 Radix

这道题比较复杂,而且涉及了进制转换,是之前没怎么涉及到的问题。但本质还是二分进制区间。还涉及到了关于字符串的处理。在init中先将数字和字母统一map到对应的下标上;然后定义一个任意进制向十进制的转换函数;由于要二分找区间需要比较大小,所以cmp函数就是看看在该进制下能不能表示这个数。对于上下界的寻找
N2进制的下界是其最大数位+1,不然表示不出这个数;上界是下界和N1的十进制的较大值+1(假设已知N1的进制)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL Map[256];//记录0-9,a-z的对应关系
LL inf = (1LL<<63)-1;//long long的最大值;

void init(){
    //将0-9和字母分别进行映射转化
    for(char c='0';c<='9';c++){
        Map[c] = c-'0';
    }
    for(char c='a';c<='z';c++){
        Map[c] = c-'a'+10;
    }
}
//这个函数要好好理解
LL convertNum10(char a[],LL radix,LL t){
    //将a转化为10进制,t是上界
    LL ans = 0;
    int len = strlen(a);
    for(int i=0;i<len;i++){
        ans = ans*radix+Map[a[i]];//任何进制转成十进制的做法
    }
    if(ans<0||ans>t) return -1;//溢出或者超过N1的十进制
    return ans;
}

int cmp(char N2[],LL radix,LL t){
    //N2的十进制和t比较
    int len = strlen(N2);
    LL num = convertNum10(N2,radix,t);//将N2转成十进制
    if(num<0) return 1;//说明溢出,肯定是N2>t;
    if(t>num) return -1;//t大
    else if(t==num) return 0;//相等
    else return 1;//num大
}

LL binarySearch(char N2[],LL left,LL right,LL t){//二分求解N2进制
    LL mid;
    while(left<=right){
        mid = left+right>>1;
        int flag = cmp(N2,mid,t);//比较转化成十进制之后谁大
        if(flag==0) return mid;
        else if(flag==-1) left = mid+1;//右区间搜寻
        else right = mid-1;//左边找
    }
    return -1;//无解
}

int findLargestDigit(char N2[]){
    //求最大的数位
    int ans = -1,len = strlen(N2);
    for(int i=0;i<len;i++){
        if(Map[N2[i]]>ans) ans = Map[N2[i]];
    }
    return ans+1;//最大的数位是ans,说明底线为ans+1,不然表示不到这个数
}

char N1[20],N2[20],temp[20];
int tag,radix;
int main(){
    init();
    scanf("%s %s %d %d",N1,N2,&tag,&radix);
    if(tag==2){//交换N1和N2
        strcpy(temp,N1);
        strcpy(N1,N2);
        strcpy(N2,temp);
    }
    //都转换成N1已知处理
    LL t = convertNum10(N1,radix,inf);//将N1从radix转成十进制
    LL low = findLargestDigit(N2);
    LL high = max(low,t)+1;
    LL ans = binarySearch(N2,low,high,t);
    if(ans==-1) printf("Impossible\n");
    else printf("%lld\n",ans);
    return 0;
}

A.1044 Shopping in Mars

做了一个小时发现还是超时,错误debug掉了,但是不懂怎么处理不等的情况。只能想到二次遍历,但这样可能导致时间复杂度太高。这是STL的代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1E5+10;
int q[N],sum[N];


int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&q[i]);
        sum[i] = sum[i-1]+q[i];//求一个前缀和,是单调递增的
    }
    int l,r;
    //先进行第一次遍历,如果有输出就结束,没有的话就遍历第二次求最小的
    int minn = (1 << 31) - 1;
    int flag=0;
    for(int i=1;i<=n;i++){
        int t = i;
        l=i,r=n;
        if(sum[i]-sum[i-1]==m) {
            printf("%d-%d",i,i);
            break;
        }
        while(l>r){
            int mid = l+r+1>>1;
            if(sum[mid]-sum[i-1]<=m) l = mid;
            else r = mid-1;
        }
        //二分找到了第一个不小于m的点
        for(int i=l;i<=n;i++){
            if(sum[i]-sum[t-1]==m) {
                printf("%d-%d\n",t,i);
                flag = 1;
                break;
            }else if(sum[i]-sum[t-1]>m){
                minn = min(sum[i]-sum[t-1],minn);
                break;
            }
        }
    }
    //拿到了最小值
    if(!flag){
        for(int i=1;i<=n;i++){
        int t = i;
        l=i,r=n;
        while(l>r){
            int mid = l+r+1>>1;
            if(sum[mid]-sum[i-1]<=minn) l = mid;
            else r = mid-1;
        }
        
        //二分找到了第一个不小于m的点
        for(int i=l;i<=n;i++){
            if(sum[i]-sum[t-1]==minn) {
                printf("%d-%d\n",t,i);
                break;
            }
        }
    }
    }
}

算法笔记的书上是这么写的

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1e5+10;
int sum[maxn];
int upper_bound(int L, int R, int x)
{
	int left = L, right = R, mid;
	while(left < right)
	{
		mid = (left + right) / 2;
		if(sum[mid] > x)
		right = mid;
		else 
		left = mid+1;
	}
	return left;
}
int main()
{
	int n, S, val, nearS = 1e9;
	scanf("%d%d", &n, &S);
	sum[0] = 0;
	for(int i = 1; i <= n; i++)
	{
		scanf("%d", &sum[i]);
		sum[i] += sum[i-1];
	}
	for(int i = 1; i <= n; i++)
	{
		int j = upper_bound(i, n+1, sum[i-1]+S);
		if(sum[j-1] - sum[i-1] == S)
		{
			nearS = S;
			break;
		}
		else if(j <= n && sum[j] - sum[i-1] < nearS)
		nearS = sum[j] - sum[i-1];
	}
	for(int i = 1; i <= n; i++)
	{
		int j = upper_bound(i, n+1, sum[i-1]+nearS);
		if(sum[j-1] - sum[i-1] == nearS)
		printf("%d-%d\n", i, j-1);
	}
}

这题关键的是二分条件的选择没选好,要二分出大于M的,结束之后如果满足等于就break,否则记录near,near定义为外部的量循环更新。统一用near表示,如果等就把S置于near,如果不是就循环更新。第二遍遍历的时候就找near,如果等于near就输出。

A.1048 Find Coins

这个题和前面的二分有点不一样,找到了二分点要把下标存下来。然后return。再分不满足条件的l和r的更新策略。(二分真的蛋疼…)

#include<bits/stdc++.h>
using namespace std;

const int N = 1E5+10;
int q[N];


int main(){
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        scanf("%d",&q[i]);
    }
    sort(q,q+n);//排个序
    int flag=0;
    for(int i=0;i<n;i++){
        int l=i,r=n-1;
        while(l<r){
            int mid = l+r+1>>1;
            if(q[i]+q[mid]==m) {
               flag=1;
               l=mid;
               break;
            }
            else if(q[i]+q[mid]<m) l=mid;
            else r=mid-1;
        }
        if(flag==1) {
            cout<<q[i]<<" "<<q[l];
            break;
        }
        else if(flag==0 && i==n-1) cout<<"No Solution";
    }
}

双指针

A. 1029 Median

此题较为简单,但是我第一次AC的代码其实效率不如答案。因为要找中间的数,所以不用把整个数组弄出来,只需要设一个计数变量为这个中间索引,两个数组边往后遍历边统计结果,到了就停止。以下是我的代码:

#include<bits/stdc++.h>
using namespace std;

const int N=2e5+10;
int s1[N],s2[N];

int main(){
    int n,m;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>s1[i];
    }
    cin>>m;
    for(int i=0;i<m;i++){
        cin>>s2[i];
    }
    int s[n+m];
    int p=0,q=0,k=0;
    while(p<n&&q<m){
        if(s1[p]<=s2[q]){
            
            s[k++]=s1[p++];
        }else{
            
            s[k++]=s2[q++];
        }
    }
    if(p==n){
        while(q<m) s[k++] = s2[q++];
    }else{
        while(p<n) s[k++] = s1[p++];
    }
    cout<<s[(n+m+1)/2-1];
    
}

其他的高效技巧和算法

打表

活用递推

A. 1093 Count PAT’s

这个题挺有意思:枚举无法在规定时间内解决问题。于是我们换种角度思考,转化成递推问题。对于一个确定位置的A,我们只需要将其左边的P的个数乘右边T的个数即可。那么我们就可以维护一个数组记录每个位置的A左边有多少个P,右边有多少个T。

#include<bits/stdc++.h>
#include<cstring>
using namespace std;
const int N = 1e6+10;
const int MOD = 1000000007;
char str[N];
int leftnump[N]={0};
int main(){
    cin.getline(str,N);
    int len=strlen(str);
    for(int i=0;i<len;i++){
        if(i>0){
            leftnump[i]=leftnump[i-1];
        }

        if(str[i]=='P') leftnump[i]++;
    }

    int ans=0, rightnumt=0;
    for(int i=len-1;i>=0;i--){
        if(str[i]=='T') rightnumt++;
        else if(str[i]=='A') ans = (ans+leftnump[i]*rightnumt)%MOD;
    }
    cout<<ans;
}

5.1 数学问题

PAT B1019 数字黑洞

经典的数字转数组,数组转数字。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值