PAT甲级刷题笔记(6)

96. 最大连续子序列和(1007)
本题要求求出最大连续子序列和即对应首尾元素。利用动态规划思想,关键在于如何确定状态转移方程。
考虑以A[i]为结尾的最大连续序列和数组dp[i],则dp[i]=max{A[i],dp[i-1]+A[i]},又有边界条件A[0]=dp[0],即可求解。
由于还需要确定子序列范围,额外定义一个数组,保留所在连续子序列的前一结点
注意:(1)全为负数情况,输出最大和0,首尾元素
(2)全为负数或0的情况,输出0 0 0。

#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn=10010;
int A[maxn],dp[maxn],pre[maxn];

int main(){
    int n;
    scanf("%d",&n);
    bool flag=true;//判断是否为全负
    for(int i=0;i<n;i++){
        scanf("%d",&A[i]);
        if(A[i]>=0) flag=false;
    }
    if(flag){
        printf("0 %d %d",A[0],A[n-1]);
        return 0;
    }
    dp[0]=A[0];
    pre[0]=0;
    for(int i=1;i<n;i++){
        if(dp[i-1]+A[i]>A[i]){
            dp[i]=dp[i-1]+A[i];
            pre[i]=i-1;
        }
        else{
            dp[i]=A[i];
            pre[i]=i;
        }
    }
    int k=0;//保存最大连续子序列和的结尾下标
    for(int i=1;i<n;i++){
        if(dp[i]>dp[k]){
            k=i;
        }
    }    
    int s=k;//s保存序列首元素下标
    while(pre[s]!=s){
        s=pre[s];
    }
    printf("%d %d %d",dp[k],A[s],A[k]);
    return 0;
}

97. 最喜欢颜色序列(1045)
本题要求从序列中取出不喜欢的数字,并确定剩余序列中按喜欢子序列顺序选择的最大长度。
(1)去除不喜欢的元素
(2)可以用LIS方法:状态转移方程为dp[i]=max{1,dp[j]+1}(j<i),转移条件是A[i]=A[j],或A[i]在喜欢序列中位置比A[j]靠后,可以用一个辅助数组存储喜欢数字出现顺序。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10010;
int order[210]={0};//存储喜欢序列的序号,>0也表示数字是喜欢序列中元素
int num[maxn],valid[maxn],dp[maxn];

int main(){
    int n,m,l;
    scanf("%d%d",&n,&m);
    int color;
    for(int i=1;i<=m;i++){
        scanf("%d",&color);
        order[color]=i;
    }
    scanf("%d",&l);
    int numVaild=0;
    for(int i=0;i<l;i++){
        scanf("%d",&num[i]);
        if(order[num[i]]!=0) {//是喜欢序列中的元素
            valid[numVaild++]=num[i];
        }
    }
    //确定最长不降子序列
    int ans=-1;//记录最长序列长
    for(int i=0;i<numVaild;i++){
        dp[i]=1;
        for(int j=0;j<i;j++){
            if(order[valid[i]]>=order[valid[j]]){
                //当i在喜欢序列中排在j后面或等于j时,更新长度
                dp[i]=dp[j]+1;
            }
        }
        ans=max(ans,dp[i]);
    }
    printf("%d",ans);
    return 0;
}

当然本题也可以用LCS做,不同的地方在于由于可以公共子序列可以重复,所以在A[i]=B[j]时,喜欢序列A不应该直接探查下一个,A[i]还可以继续比较,因此状态转移方程在A[i]=B[j]修改为:

dp[i][j]=max(dp[i-1][j],dp[i][j-1])+1

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10010;
int color[210];
int num[maxn];
int dp[210][maxn];

int main(){
    int n,m,l;
    scanf("%d%d",&n,&m);    
    for(int i=1;i<=m;i++){
        scanf("%d",&color[i]);        
    }
    scanf("%d",&l);    
    for(int i=1;i<=l;i++){
        scanf("%d",&num[i]);        
    }
    //边界
    for(int i=0;i<=m;i++){
        dp[i][0]=0;
    }
    for(int i=0;i<=l;i++){
        dp[0][i]=0;
    }
    for(int i=1;i<=m;i++){
        for(int j=1;j<=l;j++){
            if(color[i]==num[j]){
                dp[i][j]=max(dp[i-1][j],dp[i][j-1])+1;
            }
            else{
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
            //printf("%d ",dp[i][j]);
        }
        //printf("\n");
    }
    
    printf("%d",dp[m][l]);
    return 0;
}

98. 最长对称子串(1040)
本题要求求出字符串中最长的对称串长度,可以将字符串对称,用LCS算法。
不同地方在于,本题要求的回文字串是要连续的,因此这里用LCS也要改为求最长相同连续子串,所以在A[i]!=B[j]时,dp[i][j]直接变为0。

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=1010;
char str[maxn],revStr[maxn];
int dp[maxn][maxn];

int main(){
    scanf("%[^\n]",str);
    int len=strlen(str);
    for(int i=0;i<len;i++){
        revStr[i]=str[len-i-1];        
    }
    for(int i=0;i<=len;i++){
        dp[i][0]=0;
        dp[0][i]=0;
    }
    int maxlen=1;
    for(int i=1;i<=len;i++){
        for(int j=1;j<=len;j++){
            if(str[i-1]==revStr[j-1]){
                dp[i][j]=dp[i-1][j-1]+1;
                maxlen=max(maxlen,dp[i][j]);
            }
            else{
                dp[i][j]=0;
            }
        }
    }
    printf("%d",maxlen);
    return 0;
}


99. 找硬币(1068)
本题要求从给定序列中选择部分数字,使其和等于金额。
方法一:DFS递归求解,
先将硬币排序,每种硬币有选与不选两种情况,分别递归,当超过金额时返回,当等于和时,则需要与其它方案比较,选出更小的。

但是对于本题,有两个测试点会超时。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=10010;
int n,m;
int num[maxn];
vector<int> temp,ans;

void DFS(int sum,int index){//当前已选硬币总价值,当前待处理序号
    if(sum>m||index>=n) return;
    if(sum==m){
        if(ans.size()==0){//第一次发现解
            ans=temp;
        }
        return;
    }
    
    //选第index个硬币
    temp.push_back(num[index]);
    //printf("push:%d\n",num[index]);
    DFS(sum+num[index],index+1);
    //不选第index个硬币
    temp.pop_back();
    //printf("pop:%d\n",num[index]);
    DFS(sum,index+1);    
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        scanf("%d",&num[i]);
    }
    sort(num,num+n);
    DFS(0,0);
    if(ans.size()==0){
        printf("No Solution");
    }
    else{
        for(int i=0;i<ans.size();i++){
            printf("%d",ans[i]);
            if(i<ans.size()-1) printf(" ");
        }
    }
    return 0;
}

方法二:01背包问题方法
在这里,总金额m对应背包容量,物品的价值和重量则都对应硬币面额。
注意:(1)dp[m]!=m对应于无法在背包容量为m时,凑不出价值为m物品,即无解;
(2)多解时要求按字典序,所以可以先将硬币按大小排序,我们的目的是优先保存最小金额小的,但是dp是从右开始更新的,所以应该是从大到小排;
(3)还要求输出硬币组合情况,那么还需要一个辅助数组记录物品的存放情况。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=10010;
const int maxv=110;
int n,m;
int w[maxn],dp[maxv];
bool choice[maxn][maxv];//choice[i][v]表示dp[i][v]抉择时是否加入了第i个硬币
bool flag[maxv];//表示第i个硬币是否选择
vector<int> temp,ans;

bool cmp(int a,int b){
    return a>b;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
    }
    sort(w+1,w+n+1,cmp);
    //初始边界
    for(int i=0;i<=m;i++){
        dp[i]=0;
    }
    for(int i=1;i<=n;i++){
        for(int v=m;v>=w[i];v--){
            if(dp[v]<=dp[v-w[i]]+w[i]){
                //放入第i件物品
                /*等于时表明至少有两种方案,选择w[i]和不选w[i],在相等时仍然选择w[i]
                是因为,越往后的w[i]越大,在总金额不变时,前面的金额就越小,从而保证选择字典序小的
                */
                dp[v]=dp[v-w[i]]+w[i];
                choice[i][v]=1;
            }
            else choice[i][v]=0;
        }
    }
    
    if(dp[m]!=m){
        printf("No Solution");
    }
    else{//查找选择硬币情况
        //这个过程和LCS算法里从辅助矩阵中找出子序列过程类似
        int k=n,num=0,v=m;
        while(k>=0){
            if(choice[k][v]==1){
                flag[k]=true;
                v-=w[k];
                num++;
            }
            else flag[k]=false;
            k--;
        }
        for(int i=n;i>=1;i--){
            if(flag[i]){
                printf("%d",w[i]);
                num--;
                if(num>0) printf(" ");
            }
        }
    }
    return 0;
}

100. 栈(1057)
本题要求给出任意状态下栈的中位数,如果栈为空,碰到Pop和查找中间元素作出Invalid判断。使用分块思想,把栈内元素划分成n^1/2的大小,用辅助数组存储每块的数量,每次插入后,只需确定插入块,修改块内元素大小。而查询时,也是先确定所在块,在根据之前各块大小,确定查询的中位数在所在块中的序号。

#include<cstdio>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=100010;
const int sqrN=316;//1000001开根号结果,为最大所需块数

stack<int> st;//实现初入栈操作
int block[sqrN]={0};//记录每块中元素个数
int table[maxn]={0};//hash表,用于表示x当前存在个数
int n;

void Push(int x){//入栈及其相应处理
    st.push(x);
    block[x/sqrN]++;//对应块元素个数+1
    table[x]++;//对应元素个数+1
}

void Pop(){
    if(st.empty()) {//栈为空
        printf("Invalid\n");
        return;
    }
    int x=st.top();
    st.pop();
    block[x/sqrN]--;//对应块元素个数-1
    table[x]--;//对应元素个数-1
    printf("%d\n",x);
}

void Median(){
    if(st.empty()) {//栈为空
        printf("Invalid\n");
        return;
    }
    //确定中位数序号
    int K=st.size();
    if(K%2==0) K/=2;
    else K=(K+1)/2;
    int sum=0,index=0;//已累计元素个数,块号
    //确定中位数所在块号
    while(sum+block[index]<K){
        sum+=block[index];
        index++;
    }
    //在对应块内找到查找数字
    int num=sqrN*index;//所在块第一个数字
    while(sum+table[num]<K){
        sum+=table[num];//累加块内数字个数
        num++;
    }
    printf("%d\n",num);
}

int main(){
    
    scanf("%d",&n);
    char str[20];
    int x;
    for(int i=0;i<n;i++){
        scanf("%s",str);
        if(strcmp(str,"Push")==0){
            scanf("%d",&x);
            Push(x);
        }
        else if(strcmp(str,"Pop")==0){
            Pop();
        }
        else if(strcmp(str,"PeekMedian")==0){
            Median();
        }
    }
    
    return 0;
}

101. 螺旋数组(1105)
本题要求把给定数组排成螺旋数组
(1)螺旋数组大小m和n如何确定:求出N与sqrt(N)最近的因子即为n;
(2)如何确定各元素的位置:螺旋数组可以看作按圈填入,每圈又分为右、下、左、上移填入四个步骤;

#include<cstdio>
#include<math.h>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=10010;
int m,n;

int A[maxn],matrix[maxn][maxn];
void m_n(int N){
    int sqrN=(int)sqrt(1.0*N);
    for(int i=sqrN;i>=1;i--){
        if(N%i==0) {
            n=i;
            m=N/n;
            return;
        }
    }    
}

bool cmp(int a,int b){
    return a>b;
}

int main(){
    int N;
    scanf("%d",&N);
    for(int i=0;i<N;i++){
        scanf("%d",&A[i]);
    }
    sort(A,A+N,cmp);
    //确定m和n的大小
    m_n(N);
    //printf("%d %d",m,n);
    int num=0,x=0,y=0;//num表示已填入螺旋数组个数,x,y为所在下标
    int cycle=1;//当前圈数
    //填入螺旋数组
    if(N==1) {//1个元素特判
        printf("%d",A[0]);
        return 0;
    }
    while(num<N){        
        //左移
        while(num<N&&x<n-cycle){
            matrix[y][x] = A[num++];
            x++;
        }
        //下移
        while(num<N&&y<m-cycle){
            matrix[y][x] = A[num++];
            y++;
        }
        //右移
        while(num<N&&x>=cycle){
            matrix[y][x] = A[num++];
            x--;
        }
        //上移
        while(num<N&&y>=cycle){
            matrix[y][x] = A[num++];
            y--;
        }
        //填完回到起点,现在移动到内一圈的起点
        x++;
        y++;
        //printf("x=%d y=%d,num=%d\n",x,y,num);
        cycle++;
        //对完全平方数,最中心的数需要特别处理,否则一直无法填入,陷入死循环
        if(n==m&&num==N-1){
            matrix[x][y]=A[num++];
        }
    }
       
    
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            printf("%d",matrix[i][j]);
            if(j<n-1) printf(" ");
        }
        printf("\n");
    }
    
    return 0;
}

102. 银行排队(1017)
本题要求计算用户的平均等待时间。
等待时间=开始处理时间 - 抵达时间。所以当所有窗口的最早空闲时间>抵达时间时就需要等待。
(1)用结构体存储每个用户的抵达时间,处理时间,和等待时间(全部先化为秒)
(2)先按抵达时间顺序对用户排序,确定优先处理顺序
(3)设立一个窗口数组,存储该窗口的下一空闲时刻。同时每处理一个用户,要随时更新最早窗口空闲时间。

#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
const int maxn=10010;
const int maxk=110;
const int openTime=8*3600,closeTime=17*3600;
int windows[maxk];//存储窗口下一空闲时间
int n,k;

struct Customer{
    int arriveTime;
    int ProcessTime;
    //int waitTime=0;
}cust[maxn];

bool cmp(Customer x,Customer y){
    return x.arriveTime<y.arriveTime;
}

int findMinTime(){
    int idx=0;
    for(int i=1;i<k;i++){
        if(windows[idx]>windows[i]) {            
            idx=i;
        }
    }
    return idx;
}

int main(){
    
    scanf("%d%d",&n,&k);
    int hh,mm,ss;
    for(int i=0;i<n;i++){
        scanf("%d:%d:%d %d",&hh,&mm,&ss,&cust[i].ProcessTime);
        cust[i].arriveTime=hh*3600+mm*60+ss;
        cust[i].ProcessTime*=60;
    }
    //按抵达时间排序
    sort(cust,cust+n,cmp);
    int sum=0,numValid=0;//总等待时间,有效客人数
    int idx=0;//当前窗口最早空闲时间,idx存储最早空闲时间下标
    fill(windows,windows+k,openTime);//初始化所有窗口空闲时间都是开门时间
    for(int i=0;i<n;i++){
        if(cust[i].arriveTime>closeTime){//17点后来的
            break;
        }
        numValid++;       
        if(cust[i].arriveTime<windows[idx]){//需要等待
            sum += windows[idx] - cust[i].arriveTime;
            //printf("sum=%d\n",sum);
            //更新等待时间
            windows[idx] += cust[i].ProcessTime;
            idx=findMinTime();            
        }
        else{//不需等待的情况
            windows[idx] = cust[i].arriveTime+cust[i].ProcessTime;
            idx=findMinTime();
        }
    }
    double avgWait =(1.0*sum)/(60*numValid);
    printf("%.1f",avgWait);
    return 0;
}

103. 银行排队(1014)
本题要求根据排队情况,确定每个用户是否能够办理业务
(1)设立K个队列模拟排队情况,
(2)先把队列全部填满,之后按每个队列最快出队时间来推进时间前进,此时空出空位
下一个客户进队;之后更新最快出队时间,直到时间到达5:00,或者所用用户都已出队。

测试点4卡了很久,发现可能是下面这个例子情况:

2 2 4 4
10 540 540 2
1 2 3 4

正确答案应该是:

08:10
17:00
17:10
Sorry  

如果不在39添加LineTime[i]<close 的判断条件,那么已经不能服务的队列2会干扰选择出当前最快完成队列的选择(因为502<510),就会以为所有队列都超过17.00了可以break,但是实际上第一个队列还有一个540分钟的还没办理,即使他要办理到17:10,也必须给他办理完成。

#include<cstdio>
#include<queue>
using namespace std;
const int maxk=1010;
const int open=8*60;
const int close=17*60;
queue<int> wait[25];//窗口队列
int LineTime[25];//存储各窗口当前的时间

int Process[maxk],Finish[maxk];


int main(){
    int n,m,k,q;
    scanf("%d%d%d%d",&n,&m,&k,&q);
    for(int i=1;i<=k;i++){
        scanf("%d",&Process[i]);        
    }
    //先把队列填满,作为初始排队情况
    for(int i=0;i<n;i++){
            LineTime[i]=open;
    }
    int num=1;
    while(num<=m*n&&num<=k){
        for(int i=0;i<n;i++){
            //把每个用户序号存入队列
            wait[i].push(num++);
            if(num>k) break;
        }
    }
    
    int time=open,numValid=0;//时间和办理完成业务人数 
    int index=0,id;//空出的队列序号,客户序号    
    while(numValid<=num){//当前队列还有客户没办理业务        
        
        //找出各队列中最早出现空闲的队列
        int minTime=24*60;
        for(int i=0;i<n;i++){
            if(!wait[i].empty()&&LineTime[i]<close){
                //第二个条件是避免已经不能办理业务的队列,对还有剩余元素,且它的办理时间将会超过5点的队列进行干扰
                id=wait[i].front();            
                int outLine=LineTime[i]+Process[id];//各个队列当前队首离队时间
                //printf("id=%d,出队时间=%d\n",id,outLine);
                if(minTime>outLine){
                    minTime=outLine;
                    index=i;
                }
            }
        }
        if(LineTime[index]>=close){//当前所有队列时间过了17点,不再接受办理
            break;
        }
        //printf("LineTime[index]=%d\n",LineTime[index]);
        id=wait[index].front();
        LineTime[index]+=Process[id];//更改改队列的时间
        wait[index].pop();
        Finish[id]=LineTime[index];
        //printf("id=%d,Finish=%d\n",id,Finish[id]);
        numValid++;//有效办理业务客户数加一        
        time=LineTime[index];        
        if(time>=close){//若此时时间过了5点就不再让人入队,但队列里可能还有没处理完
            continue;
        }
        else if(time<=close&&num<=k) {//当前还有人没进队,且还没过5点,则令下一位进队
            wait[index].push(num++);
            //printf("time=%d,%d入队\n",time,num-1);
        }
    }
    //printf("numValid=%d\n",numValid);
    int query;
    for(int i=0;i<q;i++){
        scanf("%d",&query);
        //不能根据序号与有效办理业务的人数大小关系输出,因为序号大的人可能在别的队列成功办理了
        if(Finish[query]>0) printf("%02d:%02d\n",Finish[query]/60,Finish[query]%60);
        else{
            printf("Sorry\n");
        }
    }
    
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值