PAT甲级刷题笔记(4)

map专题

61. 火星数字(1011)

本题要求实现火星数字和地球数字间的转换,核心任务有:

(1)如何读入,N给定了读入数据个数,但是每个数据有几位数,是数字还是字母不确定;

——>同一用字符串读取,由于中间可能有空格,采用getling()
(2)映射关系是双向的,从数字到单词和单词到数字的映射都有

——>根据首字符判断输入的是地球数字还是火星文,地球数字到火星文可以用字符串数组,而从火星文到地球数字用map。

从地球数字到火星文简单,只需做一个进制转化去字符串数组中查找即可。从火星文到地球数字根据是否有空格分类讨论,有空格说明是两位数(除了13的倍数,这个要特别判断)。

注意:13的倍数输出不能带tret(火星0);

#include<iostream>
#include<cstdio>
#include<string>
#include<map>
using namespace std;
//数字映射到字符串
string thirteen[13]={"tret","jan","feb", "mar", "apr", "may",
                     "jun", "jly", "aug", "sep", "oct", "nov", "dec"};
string times[12]={"tam", "hel", "maa", "huh", "tou", "kes",
                  "hei", "elo", "syy", "lok", "mer", "jou"};
//字符串映射到数字
map<string,int> mars13,mars13time;

void mapping(){//实现字符串到数字的映射
    for(int i=0;i<13;i++){
        mars13[thirteen[i]]=i;
    }
    for(int i=0;i<12;i++){
        mars13time[times[i]]=(i+1)*13;
    }
    
}

void E_to_M(string s){
    int num=0;
    for(int i=0;i<s.length();i++){
        num=num*10+s[i]-'0';
    }
    if(num/13!=0){
        cout<<times[num/13-1];
        if(num%13==0) {
            cout<<endl;
            return;
        }
        else cout<<" ";//还有尾数
    }        
    cout<<thirteen[num%13]<<endl;
    
}

void M_to_E(string s){
    string temp1,temp2;
    if(s.find(" ")!=string::npos){//在s中发现空格,说明有两位数
        int t=s.find(" ");
        temp1=s.substr(0,t);
        temp2=s.substr(t+1,s.length()-t-1);
        int num=mars13time[temp1];
        num+=mars13[temp2];
        cout<<num<<endl;
        return;
    }
    map<string,int>::iterator it=mars13.find(s);
    if(it!=mars13.end()){
        int num=mars13[s];
        cout<<num<<endl;
    }
    else{
        int num=mars13time[s];
        cout<<num<<endl;
    }
    
}

int main(){
    mapping();
    int n;
    //cin>>n;
    scanf("%d",&n);
    getchar();//吸收换行
    for(int i=0;i<n;i++){
        string str;
        getline(cin,str);
        //cout<<str<<endl;
        if(str[0]>='0'&&str[0]<='9'){
            //输入的是数字
            E_to_M(str);
        }
        else{
            M_to_E(str);
        }
    }
    return 0;
}

语法:(1)mp.find(key),寻找键值的映射值,若找到返回下标,查找失败则返回end();

(2)str.substr(start,len),返回子串,start为起始位置,len为字串长度;

(3)读入带空格字符串使用getline(cin,str)(但舍弃换行符),注意要使用gelchar()先把n读入后的回车吸收掉,不然会当作一条空字符串读入。

当然我的做法其实太过复杂了,对付火星文到数字的转化其实不必分两位考虑,可以直接建立全部字符串与数字的映射关系,把所有的字符串都构造出来存入 map。

#include<iostream>
#include<cstdio>
#include<string>
#include<map>
using namespace std;

string thirteen[13]={"tret","jan","feb", "mar", "apr", "may",
                     "jun", "jly", "aug", "sep", "oct", "nov", "dec"};
string times[13]={"tret","tam", "hel", "maa", "huh", "tou", "kes",
                  "hei", "elo", "syy", "lok", "mer", "jou"};//加上tret便于统一处理

string numToStr[170];//数字->火星文
map<string,int> strToNum;//火星文->数字

void init(){//实现火星文和数字间的映射
    for(int i=0;i<13;i++){
        numToStr[i]=thirteen[i];//个位0-12,十位为0
        numToStr[i*13]=times[i];//十位为0-12,个位为0
        strToNum[thirteen[i]]=i;
        strToNum[times[i]]=i*13;
    }
    for(int i=1;i<13;i++){//十位
        for(int j=1;j<13;j++){
            //个位,从1开始是因为上一个循环已覆盖13倍数,且火星文13倍数不需要加tret
            string str=times[i]+" "+thirteen[j];
            numToStr[i*13+j] = str;
            strToNum[str] = i*13+j;
        }
        
    }
    
}


int main(){
    init();
    int n;
    scanf("%d",&n);
    getchar();//吸收换行
    for(int i=0;i<n;i++){
        string str;
        getline(cin,str);
        //cout<<str<<endl;
        if(str[0]>='0'&&str[0]<='9'){
            //输入的是数字
            int num=0;
            for(int j=0;j<str.length();j++){
                num =num*10+str[j]-'0';                
            }
            cout<<numToStr[num]<<endl;
        }
        else{
            cout<<strToNum[str]<<endl;
        }
    }
    return 0;
}

62. 主导色彩(1054)

本题本质上是要求找出矩阵中出现次数占一半以上的数据。由于矩阵很大,直接建立矩阵大小的数组很可能超出内存,因此采用map。

最后一个测试点卡了,仔细思考后发现是只有1个元素的数组,初始化映射值后循环结束没有机会输出了,因此加上特判。

#include<iostream>
#include<cstdio>
#include<map>
using namespace std;

map<int,int> color;

int main(){
    int n,m;
    int t;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){            
            scanf("%d",&t);
            if(n*m==1) {//只有一个元素的矩阵,直接输出
                printf("%d",t);
                return 0;
            }
            map<int,int>::iterator it =color.find(t);            
            if(it==color.end()){
                //第一次出现置位1
                color[t]=1;
            }
            else{
                color[t]++;
                if(color[t]>(n*m)/2){
                   printf("%d",t);
                   break;
                }                
            }            
        }
    }    
    
    return 0;
}

不过我在测试过程也发现,即使不给映射值初始化(即只有else那部分),直接++也能AC,似乎映射到int默认初值就是0(待确认)。

63. 说话形式(1071)

本题是要得到输入字符串中出现频率最高的单词及其频率,难点是解决以下问题:

(1)读入,以空格划分,以回车结束。由于cin不能读入回车,无法作为读入终止判断,因此不能直接读入单个单词,采用getline()一次性读入。
(2)统计单词出现次数,不考虑出字母与数字以外符号,不区分大小写;对"Can要跳过"识别出can,因此需要把大写字母化成小写,去掉其余字符,取单个单词存储。

#include<iostream>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
using namespace std;

map<string,int> num;
bool check(char c){
    //判断字符是否是数字或字母
    if(c>='0'&&c<='9') return true;
    if(c>='a'&&c<='z') return true;
    if(c>='A'&&c<='Z') return true;
    return false;
}

int main(){
    string s;
    getline(cin,s);
    int i=0;   
    while(i<s.length()){
        while(i<s.length()&&!check(s[i])){
            //非数字字母跳过
            i++;
        }
        string word;
        while(i<s.length()&&check(s[i])){
            if(s[i]>='A'&&s[i]<='Z'){
            //小写字母比大写字母多32个字符
                s[i] +=32;
            }
            word+=s[i];
            i++;
        }
        if(word!=""){//单词非空,进行统计
            map<string,int>::iterator it=num.find(word);
            if(it!=num.end()) num[word]++;
            else num[word]=1;
        }        
    }
    string ans;
    int Max=0;
    for(map<string,int>::iterator it=num.begin();it!=num.end();it++){
        if(it->second>Max){
            Max=it->second;
            ans=it->first;
        }
    }
    cout<<ans<<" "<<Max;
    
    return 0;
    
}

语法:map的遍历采用迭代器,it->first为键值,it->second为映射值。

63. 数字图书馆(1022)

本题要求根据图书的名字、作者、出版社等信息查询满足条件的书号,并顺序输出。核心问题:

(1)读入,字符串间用getline读入每行;(注意,key有多个关键词,需分别读入)
(2)建立字符串信息与学号间的map关系,由于存在一对多的映射,应使用map<string,set<int>>建立映射关系,为便于统一查询处理,年份也用字符串存储。

本题可以不必采用结构体存储信息,因为我们使用结构体很大原因是为了保持同一本书不同信息间的关联性,并且还能够按需求进行排序,但使用map和set都实现这两个需求,并且本题只涉及其余信息与书号间的映射,使用map就够了。

#include<iostream>
#include<stdio.h>
#include<set>
#include<map>
#include<string>
#include<string.h>
using namespace std;

map<string,set<int>> mpName,mpAuthor,mpKey,mpPub,mpYear;

void query(map<string,set<int>> &mp,string& str){
    if(mp.find(str)==mp.end()) printf("Not Found\n");
    else {
        for(set<int>::iterator it=mp[str].begin();it!=mp[str].end();it++){
            printf("%07d\n",*it);
        }
    }
}


int main(){
    int n,m;
    int id;
    string name,author,key,publisher,year;  
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&id);
        getchar();
        //getline之间不需要再getchar()
        getline(cin,name);       
        getline(cin,author);        
        while(cin>>key){//key有多个关键字需单独处理
            mpKey[key].insert(id);
            char c=getchar();
            if(c=='\n') break;
        }        
        getline(cin,publisher);
        getline(cin,year);
        
        mpName[name].insert(id);
        mpAuthor[author].insert(id);        
        mpPub[publisher].insert(id);
        mpYear[year].insert(id);        
    }
    scanf("%d",&m);
    string temp;
    int order;
    for(int i=0;i<m;i++){
        scanf("%d: ",&order);
        getline(cin,temp);
        printf("%d: ",order);
        cout<<temp<<endl;
        if(order==1) query(mpName,temp);
        else if(order==2) query(mpAuthor,temp);
        else if(order==3) query(mpKey,temp);
        else if(order==4) query(mpPub,temp);
        else query(mpYear,temp);
    }
    return 0;
}

语法:(1)getline()读入时吸收了回车,因此getline之间不需要再加getline,

(2)对于一行存在空格,以回车结束的字符串,可以用如下形式分别读入中间字符串


   while(cin>>key){
            char c=getchar();//吸收空格
            if(c=='\n') break;
        } 

(3)map<string,<set>>的用法,插入—mpName[name].insert(id),遍历输出:

for(set<int>::iterator it=mp[str].begin();it!=mp[str].end();it++){
            printf("%07d\n",*it);
        }

65. 出栈序列(1051)

本题要去判断给定序列能否作为出栈序列。根据栈的先进后出性质,以及入队序列的递增性,可以得到如下依据:

(1)若该元素小于上一个出栈元素,说明该元素已入过栈,则直接检查是否与栈顶元素是否相等;
(2)若该元素大于上一个出栈元素,则将入队序列中剩余<=该元素的数字入栈,然后该元素出栈
上述过程不可以爆栈,否则错误。

#include<iostream>
#include<stdio.h>
#include<stack>
using namespace std;

int num[8];

bool isPop(int m,int n){
    stack<int> st;
    int lastPop=-1;//上一个出栈元素
    int j=1;//当前待入栈元素
    int stackNum=0;//当前栈内元素
    for(int i=0;i<n;i++){
        if(num[i]<lastPop){
            //小于上一个出栈元素,说明已入过栈
            if(st.top()!=num[i]){
                
                return false;
            }
            else{
                st.pop();
                //cout<<num[i]<<"出栈"<<endl;
                lastPop=num[i];
                stackNum--;
            }
        }
        else{//大于上一个出栈元素,,当前<=num[i]的元素入栈
            
            while(j<=num[i]){
                st.push(j);
                //cout<<j<<"入栈"<<endl;
                j++;
                stackNum++;
                if(stackNum>m) return false;
            }
            st.pop();
            //cout<<num[i]<<"出栈"<<endl;
            stackNum--;
            lastPop=num[i];
        }
    }
    return true;
}

int main(){
    int n,m,k;
    scanf("%d%d%d",&m,&n,&k);
    for(int i=0;i<k;i++){
        for(int j=0;j<n;j++){
            scanf("%d",&num[j]);            
        }
        if(isPop(m,n)){
            printf("YES\n");
        }
        else{
            printf("NO\n");
        }
    }
    return 0;
}

语法:stack的用法,入栈st.push(),出栈st.pop(),取栈顶元素st.top()

66. 老鼠与大米(1056)

本题要求选出每组重量最大的老鼠进入下一轮,直到选出最终的老鼠。具体实现如下:

(1)根据输入序号序列排序并分组
(2)分组内选出最大数据,进入下一轮,同时对淘汰元素的排名作保存(排名等于剩余元素+1,即当前分组数)。我们是删除原元素,还是把最大值加入新数组呢?
注意到下一轮老鼠也要按之前的先后关系分组,因此可以用队列,先从头遍历出队元素,遍历过程中保存下本组最大质量老鼠,重新入队直到老鼠数量为1
注意:我们把序号入队,这样方便根据序号存储对应的排名,(用重量就需要查找),这也是使用结构体的原因。

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;

struct Mice{
    int weight,rank;
}mice[1010];

queue<int> q;

int main(){
    int Np,Ng;
    scanf("%d%d",&Np,&Ng);
    for(int i=0;i<Np;i++){
        scanf("%d",&mice[i].weight);        
    }
    int order;
    for(int i=0;i<Np;i++){
        //把序号入队
        scanf("%d",&order);
        q.push(order);
    }
    while(q.size()!=1){
        //计算组数
        int sum=q.size();//sum表示当前队列长度
        int group =sum/Ng;
        if(sum%Ng!=0) group++;
        //分组筛选最大值,最后一组个数可能不同
        for(int i=0;i<group;i++){
            int len=Ng;
            if(i==group-1&&sum%Ng!=0) len=sum%Ng;//最后一组大小可能需要修改
            int k=q.front();//k存储组内最重老鼠编号
            for(int j=0;j<len;j++){
                int now=q.front();
                if(mice[now].weight>mice[k].weight){
                    k=now;
                }
                //先给所有老鼠排名group+1,晋级的老鼠会被之后的正确排名覆盖
                mice[now].rank=group+1;
                q.pop();
            }
            q.push(k);
        }        
    }
    mice[q.front()].rank=1;//最后一只老鼠是第一名
    printf("%d",mice[0].rank);
    for(int i=1;i<Np;i++){        
        printf(" %d",mice[i].rank);
    }
    
    return 0;
}

语法:队列queue的用法,如入队q.push(k),出队q.pop(),访问首\尾元素q.front()\q.back()。

67. 反转链表(1074)

本题要求按节翻转链表。方法如下:

(1)由于地址都是5位数,可以建立静态链表存储结点数据

(2)按节翻转,用中间变量存储i+2结点地址,把i+1结点指针改成指向i结点,重复该过程。(记得反转一节后,要重新修改上一节末尾元素的指针域)。

注意点:(1)孤立结点不输出,测试点5中输入的结点中存在不在链表上的结点,不应该输出;

(2)注意是按节翻转,不足一节的不翻转,开始没注意到这一点导致测试点1\4错误。

#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn=100010;
struct Node{
    int data;
    int next;
}node[maxn];

int main(){
    int head,n,k;
    scanf("%d%d%d",&head,&n,&k);
    int add;
    for(int i=0;i<n;i++){
        scanf("%d",&add);
        scanf("%d%d",&node[add].data,&node[add].next);
    }
    int head1=head;
    int num=1;
    //统计有效个数
    while(node[head1].next!=-1){
        num++;
        head1=node[head1].next;        
    }
    int group=num/k;//待翻转组数
    int j=0;
    int last,temp,temp2,head2,newhead;
    while(j<group){       
     temp=node[head].next;//存储i+1结点
     temp2;//存储i+2结点
     head2=head;//备份原来头结点地址
    for(int i=0;i<k-1;i++){
        temp2=node[temp].next;//存储i+2结点地址
        node[temp].next=head;//i+1节点改为指向i结点
        head=temp;//处理i+1节点
        temp=temp2;//下一结点改为i+2
    }
    if(j>0) node[last].next=head;
    else newhead=head;
    last=head2;//本节原头结点地址
    head = temp;//head移向下一节首结点  
    j++;
    }
    node[last].next=head;
    int i=0;
    while(node[newhead].next!=-1&&i<n-1){
        printf("%05d %d %05d\n",newhead,node[newhead].data,node[newhead].next);
        newhead=node[newhead].next;
        i++;
    }
    printf("%05d %d %d\n",newhead,node[newhead].data,node[newhead].next);
    
    return 0;
}

反思:(1)可以在静态链表结构体内再加上一个地址,方便把有效的结点移动到数组前面统一处理,用下标表示地址虽然节省空间,但限制了灵活性;

(2)由于在翻转过程中head不断移动,常导致头结点丢失,为了保存头结点,导致我的代码存在大量中间变量。有空再回来重新做下这题。

68. 找公共后缀(1032)
本题要求确定两个用链表存储的单词是否存在公共后缀。我用word2的逐个字母与word1比较,看是否存在相同,但这样时间复杂度为O(mn),会在两个测试点超时。

参考解答后,发现可以给每个结点设置一个符号位,单词1经过的字母符号位改为true,再遍历word2一定,一旦发现又flag==true那么就存在相同后缀。

#include<iostream>
#include<stdio.h>
using namespace std;

const int maxn=100010;

struct Letter{
    char data;
    int next;
    bool flag;
}let[maxn];


int main(){
    int head1,head2,n;
    scanf("%d%d%d",&head1,&head2,&n);
    int add;
    for(int i=0;i<n;i++){
        scanf("%d",&add);
        scanf(" %c %d",&let[add].data,&let[add].next);
    }
    int pre;
    for(pre=head1;pre!=-1;pre=let[pre].next){
        //遍历单词1的所有字母
        let[pre].flag=true;
    }
    for(pre=head2;pre!=-1;pre=let[pre].next){
        //遍历单词2的所有字母
        if(let[pre].flag==true){
            break;
        }
        
    }
    if(pre!=-1) printf("%05d",pre);
    else printf("-1");
    
    return 0;
}

语法:(1)scanf字符%c时,空格也会被当做字符处理,因此必须根据读入格式加入空格区分,否则会导致读入出错

(2)bool类型变量默认值为false。

69. 链表排序(1052)

本题要求在把链表按数据大小重新排序。考虑到链表上存在无效结点,因此不能直接按读入顺序存储进结构体数组中,必须先按地址存储,然后再遍历筛选出有效结点(设置序号属性),将有效结点排在前面,再对有效结点排序。

注意:测试点5是0个有效结点的特例,需要特判。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100010;

struct Node{
    int address,data,next;
    int order=maxn;//用于表示链表上的有效结点的序号
}node[maxn];

bool cmpValid(Node x,Node y){
    return x.order<y.order;
}

bool cmpData(Node x,Node y){
    return x.data<y.data;
}

int main(){
    int n,head;
    scanf("%d%d",&n,&head);
    int add;
    for(int i=0;i<n;i++){
        scanf("%d",&add);
        scanf("%d%d",&node[add].data,&node[add].next);
        node[add].address=add;
    }
    int cnt=0,pre;
    for(pre=head;pre!=-1;pre=node[pre].next){
        node[pre].order=cnt;
        cnt++;
    }
    //把有效结点移动到前面,便于按data排序
    sort(node,node+maxn,cmpValid);
    sort(node,node+cnt,cmpData);
    if(cnt==0) {
        printf("0 -1");
        return 0;
    }
    printf("%d %05d\n",cnt,node[0].address);
    for(int i=0;i<cnt-1;i++){
        printf("%05d %d %05d\n",node[i].address,node[i].data,node[i+1].address);
    }
    printf("%05d %d -1\n",node[cnt-1].address,node[cnt-1].data);
    return 0;    
}

70. 链表去重(1097)

本题要求去除绝对值相同的结点,只保留第一个结点。并要求把这些去掉重复结点单独组成一个链表。步骤:
(1)先按静态链表读入,筛选出有效结点。
(2)遍历有效结点,利用辅助数组表示当前数据绝对值是否出现过,出现过的修改序号,通过排序使重复结点排到去重后的有效结点后面输出。

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int maxn=100010;
bool num[maxn]={false};//表示该数字的绝对值是否出现

struct Node{
    int add,data,next;
    int order=2*maxn;
}node[maxn];

bool cmpValid(Node x,Node y){
    return x.order<y.order;
}

int main(){
    int head,n;
    scanf("%d%d",&head,&n);
    int address;
    for(int i=0;i<n;i++){
        scanf("%d",&address);
        scanf("%d%d",&node[address].data,&node[address].next);
        node[address].add=address;
    }
    int cnt=0,p;
    for(p=head;p!=-1;p=node[p].next){
        node[p].order=cnt++; 
        
    }    
    //这里是对整个数组排序,筛选有效结点
    sort(node,node+maxn,cmpValid);
    
    int numSame=0;
    for(int i=0;i<cnt;i++){
        //修改绝对值重复元素的序号
        int absData = abs(node[i].data);
        if(num[absData]){//该绝对值已经出现过
            node[i].order = maxn+numSame;
            numSame++;
        }
        else {
            num[absData]=true;
        }
    }
    
    //重新排序,重复结点将排到去后链表后面
    sort(node,node+cnt,cmpValid);        
    int distinct = cnt-numSame;
    for(int i=0;i<distinct-1;i++){
        printf("%05d %d %05d\n",node[i].add,node[i].data,node[i+1].add);
    }
    if(distinct>0) printf("%05d %d -1\n",node[distinct-1].add,node[distinct-1].data);
    for(int i=distinct;i<cnt-1;i++){
        printf("%05d %d %05d\n",node[i].add,node[i].data,node[i+1].add);
    }
    if(numSame>0) printf("%05d %d -1\n",node[cnt-1].add,node[cnt-1].data);
    
    return 0;
}

语法:绝对值函数,int用abs(int),double用fab(double),long long用labs(LL)。

71. 整数分解(1103)

本题要求把给定数字N分解成K个数字的P次方之和,如果有多个,选择和最大的,和相同,选择字典序最大的。
(1)确定筛选数的范围,最极端请款,K-1个数是1,那么第K个因子会达到最大因此搜索范围是:(N-K+1)开P次方-1。降序选择是为了优先选出字典序大的。
(2)对这一范围的数,进行深度优先递归(参考算法笔记)。

不过这个字典序排序确实让我迷惑,我加了一段遍历temp和ans比较的代码,但反而出错,不加反而能通过。

#include<iostream>
#include<stdio.h>
#include<math.h>
#include<vector>
using namespace std;
int N,K,P,maxSum=-1;
int maxFac;//因子的上界
vector<int> temp,ans;

//index表示当前选择到index这个数,nowK表示当前已选中因子个数,
void DFS(int index,int nowK,int sum,int Psum){
    if(nowK==K && Psum==N){//找到这样的K-P分解
        if(sum>maxSum){
            maxSum =sum;
            ans=temp;
        }
        /*
        else if(sum==maxSum){
            for(int i=0;i<K;i++){
                if(ans[i]>temp[i]){                    
                    break;
                }                
            }
        }
        
        for(int i=0;i<ans.size();i++){
            printf("%d ",ans[i]);
        }
        printf("\n");
        */
        return;
    }
    if(index==0 || nowK>K ||Psum>N) return;
    //选index数据
    temp.push_back(index);
    int Psquare =(int)pow((double)index,(double)P);    
    DFS(index,nowK+1,sum+index,Psum+Psquare);
    //不选index数据
    temp.pop_back();
    DFS(index-1,nowK,sum,Psum);
}

int main(){
    scanf("%d%d%d",&N,&K,&P);
    maxFac = (int)pow((double)(N-K+1),1.0/P)+1;
    DFS(maxFac,0,0,0);
    if(ans.size()==0) {
        printf("Impossible\n");
        return 0;
    }
    printf("%d =",N);
    for(int i=0;i<ans.size();i++){
        printf(" %d^%d",ans[i],P);
        if(i<ans.size()-1) printf(" +");
    }
    
    return 0;
}

语法:(1)对int型数据求幂,可以采用pow函数,但需要做类型转换,如int Psquare =(int)pow((double)index,(double)P);。

72. 急性脑卒中(1091)

本题要求统计3维0-1矩阵中,由1组成的,个数不小于T的块中,1的总数量。本题与算法笔记上例题类似,采用BFS思想:枚举每一个位置元素,为0跳过,为1,则使用BFS查询相邻的6个位置元素是否为1,若相邻再重复操作,直到访问完整个1块。
辅助数组:(1)记录每个位置是否入过队的inq,减少大量重复计算
(2)增量数组,表示6个方向。

#include<cstdio>
#include<queue>
using namespace std;
struct node{
    int x,y,z;//位置(x,y,z)    
}Node;

int n,m,l;//3维矩阵大小
int T;//块下界
int matrix[62][1290][130];
bool inq[62][1290][130]={false};
int X[6]={0,0,0,0,1,-1};
int Y[6]={0,0,1,-1,0,0};
int Z[6]={1,-1,0,0,0,0};


bool judge(int x,int y,int z){
    //判断位置(x,y,z)是否需要访问
    if(x<0||x>=l || y<0||y>=m || z<0||z>=n) {
        return false;
    }
    if(matrix[x][y][z]==0||inq[x][y][z]==true){
        return false;
    }
    return true;
}

int BFS(int x,int y,int z){
    int total=0;//统计块中1的个数
    queue<node> Q;
    Node.x=x,Node.y=y,Node.z=z;
    Q.push(Node);
    inq[x][y][z]=true;
    while(!Q.empty()){
        node top =Q.front();//取队首元素
        Q.pop();
        total++;
        for(int i=0;i<6;i++){
            int newX=top.x + X[i];
            int newY=top.y + Y[i];
            int newZ=top.z + Z[i];
            if(judge(newX,newY,newZ)){//新位置需要访问
                Node.x=newX,Node.y=newY,Node.z=newZ;
                Q.push(Node);
                inq[newX][newY][newZ]=true;                
            }
        }
    }
    //printf("%d\n",total);
    if(total>=T) return total;
    else return 0;
}
    
int main(){
    scanf("%d%d%d%d",&m,&n,&l,&T);    
    for(int i=0;i<l;i++){
        for(int j=0;j<m;j++){
            for(int k=0;k<n;k++){
                scanf("%d",&matrix[i][j][k]);
                
            }
            
        }
    }
    int volume=0;//块和总体积数
    //枚举每一位置
    for(int i=0;i<l;i++){
        for(int j=0;j<m;j++){
            for(int k=0;k<n;k++){
               //若元素为1,且未入过队,则发现新块
                if(matrix[i][j][k]==1&&inq[i][j][k]==false)
                  volume+=BFS(i,j,k);
            }
        }
    }
    printf("%d",volume);
    return 0;
}

73. 树的遍历(1073)

本题要求根据后序序列和中序序列得到层序遍历序列。思路是:
(1)先重建二叉树:从后序序列末尾得到根结点,再由根节点到中序序列中去划分左右子树
对左右子树递归调用上述过程,直到序列长度小于0。
(2)根据二叉树,利用BFS思想,输出层间序列。

重点掌握树的建立和层间序列输出

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
int post[40],in[40];
int n,num=0;//num表示当前打印数据个数

struct BTree{
    int data;
    BTree* lchild;
    BTree* rchild;
};

BTree* create(int pl,int pr,int il,int ir){
    if(pr<pl) return NULL;
    int inRoot;//中序序列中根节点位置
    for(int i=il;i<=ir;i++){
        if(in[i]==post[pr]){
            inRoot=i;
            break;
        }
    }    
    BTree* root = new BTree;//建立新结点存放根
    root->data = post[pr];
    root->lchild = create(pl,pl+inRoot-il-1,il,inRoot-1);
    root->rchild = create(pl+inRoot-il,pr-1,inRoot+1,ir);            
    return root;
}

void LayerOrder(BTree* root){
    queue<BTree*> q;//队列存放地址
    q.push(root);//根结点入队
    while(!q.empty()){
        BTree* now=q.front();//取出队首元素
        q.pop();
        printf("%d",now->data);
        num++;
        if(num<n) printf(" ");
        //若左右子树非空则入队
        if(now->lchild!=NULL) q.push(now->lchild);
        if(now->rchild!=NULL) q.push(now->rchild);
    }
}

int main(){
    
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&post[i]);
    }
    for(int i=0;i<n;i++){
        scanf("%d",&in[i]);
    }
    //重建树
    BTree* root = create(0,n-1,0,n-1);
    LayerOrder(root);
    return 0;
}

 语法:由于队列存放的是原数据的副本,不能直接通过修改队列修改原数据,因此建议队列存储元素的地址,利用地址去修改元素。

73. 树的遍历2(1086)

使用栈可以实现非递归的中序遍历,本题要求根据栈的入栈和出栈操作得到树的后序列。本题的关键在于发现入栈顺序是先序遍历,出栈顺序是中序遍历。

先序:根-左-右,中序:左-根-右。因此,入栈时按先序先入根-左,出栈时就会变成左-根,再入右出右,就实现了先序入栈,中序出栈。因此本题可以这样解决:
(1)先读入操作直接得到先序遍历;
(2)根据操作复现栈,得到出栈的中序序列,
(3)根据先序和中序序列得到后序序列
关于读入问题:可以先scanf一个字符串,如果发现是push,就再读入数字。

注意递归调用时的区间范围。

#include<iostream>
#include<stdio.h>
#include<stack>
#include<string.h>
using namespace std;

stack<int> tree;
int pre[32],in[32];
int num=0,n;

void postOrder(int pl,int pr,int il,int ir){
    //根据先序和中序得到后序遍历
    if(pl>pr) return;
    int inRoot;
    for(int i=il;i<=ir;i++){
        if(in[i]==pre[pl]){
            inRoot=i;
            break;
        }
    }
    int len =inRoot-il-1;
    //对左子树递归
    postOrder(pl+1,pl+len+1,il,inRoot-1);
    //对右子树递归
    postOrder(pl+len+2,pr,inRoot+1,ir);
    printf("%d",pre[pl]);
    num++;
    if(num<n) printf(" ");
}

int main(){
    
    scanf("%d",&n);
    char str[5];
    int t1=0,t2=0;
    for(int i=0;i<2*n;i++){
        scanf("%s",str);
        if(strcmp(str,"Push")==0){//说明是入栈操作
            scanf("%d",&pre[t1]);
            tree.push(pre[t1]);
            t1++;
        }
        else{//出栈操作
            in[t2++]=tree.top();
            tree.pop();
        }
    }
    postOrder(0,n-1,0,n-1);
    
    return 0;
}

75. 反转二叉树(1102)

本题要求根据每个结点的左右子树序号,得到这个树翻转之后的层序遍历和中序遍历结果。
(1)重构这课树,使用静态链表即可,关键是如何确定根结点(根节点是遍历一个树的起始位置)
由于只有根节点不是别的结点的子结点,因此在输入中没有出现的数字就是根节点序号
(2)根据树做层序和中序输出;

#include<stdio.h>
#include<queue>
using namespace std;
const int maxn=100;
int num1=0,num2=0;
int n;
struct Node{//静态链表定义树
    int lchild,rchild;
}node[maxn];

bool num[maxn]={false};//用于确定根节点

void LayerOrder(int root){
    //层序遍历,使用队列,不需要递归
    queue<int> q;
    q.push(root);
    while(!q.empty()){
        int now=q.front();
        q.pop();
        num1++;
        printf("%d",now);
        if(num1<n) printf(" ");
        if(node[now].lchild!=-1) q.push(node[now].lchild);
        if(node[now].rchild!=-1) q.push(node[now].rchild);    
    }
    
}

void inOrder(int root){
    if(root==-1) return;
    inOrder(node[root].lchild);
    num2++;
    printf("%d",root);
    if(num2<n) printf(" ");
    inOrder(node[root].rchild);
}

int main(){
    
    scanf("%d",&n);    
    char c1,c2;
    for(int i=0;i<n;i++){
        //把第一、二个序号当左右结点,直接实现翻转
        scanf("%*c%c %c",&c1,&c2);//%*c把换行吸收
        //printf("%c %c\n",c1,c2);
        if(c1=='-'){
            node[i].rchild=-1;
        }
        else{
            node[i].rchild=c1-'0';
            num[c1-'0']=true;
        }
        if(c2=='-'){
            node[i].lchild=-1;
        }
        else{
            node[i].lchild=c2-'0';
            num[c2-'0']=true;
        }
    }
    //寻找根节点
    
    int root;
    for(int i=0;i<n;i++){
        if(num[i]==false){
            root=i;
            break;
        }
    }
    LayerOrder(root);
    printf("\n");
    inOrder(root);
    
    return 0;
}

语法:scanf读入%c时,会识别换行符,因此需要考虑吸收换行,可以用getchar,也可以用scanf("%*c")的方法。

76. 供应链的总销量 (1079)

本题要求计算所有叶子结点的销售量并求和。叶子结点就是子节点数为0的结点,那么关键就是确定叶子的深度,每个叶子结点是一条从根出发的路径终点,因此这个问题本质上还是一个搜索问题,要求和所有的叶子结点,就是要遍历整棵树,可以用BFS和DFS。(本题默认0号结点为根节点,因此不必寻找根节点)
关键:
(1)读入结点数据,使用静态写法;
(2)从根结点开始用DFS遍历,计算销售量。

#include<iostream>
#include<stdio.h>
#include<math.h>
#include<vector>
using namespace std;
const int maxn=100010;
int n;
double p,r,sum=0;
struct Node{
    double sale;
    vector<int> child;
}node[maxn];

//bool num[maxn]={false};

void DFS(int root,int layer){
    if(node[root].child.size()==0){
        //抵达叶子结点                
        sum+=node[root].sale*pow(1+r,layer);
        //printf("layer=%d,root=%d,rate=%.3f,prioty=%.3f\n",layer,root,pow(1+r,layer),prioty);        
        return;
    }
    //孩子结点不为空,则进行递归访问
    for(int i=0;i<node[root].child.size();i++){
        DFS(node[root].child[i],layer+1);        
    }
}

int main(){
    
    scanf("%d%lf%lf",&n,&p,&r);
    r/=100;
    int k,point;
    for(int i=0;i<n;i++){
        scanf("%d",&k);
        if(k==0){
            scanf("%lf",&node[i].sale);
        }
        for(int j=0;j<k;j++){
            scanf("%d",&point);
            node[i].child.push_back(point);           
        }
        
    }
    /*
    int root;
    for(int i=0;i<n;i++){
        if(num[i]==false){
            root=i;
            break;
        }
    }
    */
    DFS(0,0);
    printf("%.1f",sum*p);
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本火锅店点餐系统采用Java语言和Vue技术,框架采用SSM,搭配Mysql数据库,运行在Idea里,采用小程序模式。本火锅店点餐系统提供管理员、用户两种角色的服务。总的功能包括菜品的查询、菜品的购买、餐桌预定和订单管理。本系统可以帮助管理员更新菜品信息和管理订单信息,帮助用户实现在线的点餐方式,并可以实现餐桌预定。本系统采用成熟技术开发可以完成点餐管理的相关工作。 本系统的功能围绕用户、管理员两种权限设计。根据不同权限的不同需求设计出更符合用户要求的功能。本系统中管理员主要负责审核管理用户,发布分享新的菜品,审核用户的订餐信息和餐桌预定信息等,用户可以对需要的菜品进行购买、预定餐桌等。用户可以管理个人资料、查询菜品、在线点餐和预定餐桌、管理订单等,用户的个人资料是由管理员添加用户资料时产生,用户的订单内容由用户在购买菜品时产生,用户预定信息由用户在预定餐桌操作时产生。 本系统的功能设计为管理员、用户两部分。管理员为菜品管理、菜品分类管理、用户管理、订单管理等,用户的功能为查询菜品,在线点餐、预定餐桌、管理个人信息等。 管理员负责用户信息的删除和管理,用户的姓名和手机号都可以由管理员在此功能里看到。管理员可以对菜品的信息进行管理、审核。本功能可以实现菜品的定时更新和审核管理。本功能包括查询餐桌,也可以发布新的餐桌信息。管理员可以查询已预定的餐桌,并进行审核。管理员可以管理公告和系统的轮播图,可以安排活动。管理员可以对个人的资料进行修改和管理,管理员还可以在本功能里修改密码。管理员可以查询用户的订单,并完成菜品的安排。 当用户登录进系统后可以修改自己的资料,可以使自己信息的保持正确性。还可以修改密码。用户可以浏览所有的菜品,可以查看详细的菜品内容,也可以进行菜品的点餐。在本功能里用户可以进行点餐。用户可以浏览没有预定出去的餐桌,选择合适的餐桌可以进行预定。用户可以管理购物车里的菜品。用户可以管理自己的订单,在订单管理界面里也可以进行查询操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值