CSP-J模拟赛二补题报告

日期:2023-10-01

学号:S12418

一:

总分数:40

T1【人员借调(transfer)】:20

T2【计算(calc)】:20

T3【智能公交(transit)】:0

T4【异或和(exclusive)】:0

二、比赛过程

这次模拟惨遭滑铁卢,倒数第一(悲

第一题我只考虑了如何避免“坐牢”的情况,但是我还少考虑了“破釜沉舟”这一类型,也就是知道“坐牢”无法避免,就一次性把所有事情都干完的情况(当时只编了一组样例,应该编一组事件数>1,时间>=240min的情况,这样bug就可以解决)遂得20pts

第二题我寻思既然TLE无可避免,那么就至少死的光彩一点(后来发现打表可以以空间换时间)

于是用的暴力枚举

但是为什么还是只有二十分呢?

ans数组忘清空了.........

第三题同理,还是暴力枚举(按说应该得50,但是不知为何一分没有)

讲完才知道可以用冷门的前缀和

第四题不出意料的动规,一分未得,考试时也是啥啥不会...

三、比赛分析

T1【人员借调(transfer)】:
1、题目大意

十分简洁,给定事情(也就是耗时),给定累计上限,给定重置上限时间,让你求事务做完最短用时(“坐牢”时间需要计算,最后需要重置上限)

2、比赛中的思考

这一题我想的是:当快要到上限时(也就是说这一件事要是做了一定超)重置上限(毕竟消耗更少) 但是我没有考虑当一件事做完必超上限(也就是这件事的用时>=上限)时的情况

3、解题思路

事情必须一次完成 完成时间增加到上限 上限一旦超了就会回家“坐牢”

但是!事情无法被打断!

因此我们既可以在保证不超上限的情况下完成

如果无论如何都要超 那么就一次全都完成(反正此时上限已经没用了)

4、AC代码

#include<iostream>
#include<cmath>
using namespace std;
int n,a[1005],T,min1,min2 = 0x3f3f3f3f;
int main(){
    cin>>n;
    int T_flag = 0;
    for(int i = 0;i<n;i++){
        cin>>a[i];
        T+=a[i];
        if(a[i]>=240){
            T_flag = 1;
        }
    }
    if(T_flag == 1){
        cout<<T+10080+400;
        return 0;
    }
    int now = 0;
    for(int i = 0;i<n;i++){
        if(a[i]+now<240){
            now+=a[i];
            min1+=a[i];
        }
        else{
            min1+=400+a[i];
            now = a[i];
        }
        
    }
    if(T>=240){
        min2 = 10080+400+T;
    }
    cout<<min(min1+400,min2);
    
    
    return 0;
}

T2【计算(calc)】:
1、题目大意

有多组输入。

给定下限,上限,k,让你求各位数字和=k且积最大且数字本身最小

2、比赛中的思考

我用的是暴力枚举,套了两个自定义函数,最后一个排序

(后来时间超了)

3、解题思路

暴力,但不是纯暴力。

开局先打表,求出每个数字各位之和和各位数字之积,复杂度O(n)

然后套输入循环(必须要套)

接着枚举(但是速度更快,就像直接查数学公式永远比推导要快)

最后输出即可

4、AC代码

#include<iostream>
#include<cstring>
using namespace std;
int he[5000005],ji[5000005],sum,num = 1;

int main(){
    ji[0] = 1;
    for(int i = 1;i<=5000000;i++){
        he[i] = he[i/10]+i%10;
        
        ji[i] = ji[i/10]*(i%10);
    }
    int t;
    cin>>t;
    while(t--){
        int m,n,k;
        cin>>m>>n>>k;
        int max_index,max_ji = -1;
        for(int i = m;i<=n;i++){
            if(he[i] == k){
                if(ji[i]>max_ji){
                    max_index = i;
                    max_ji = ji[i];
                }
            }
        }
        cout<<max_index<<" "<<max_ji<<endl;
        
    }
    
    return 0;
}

T3【智能公交(transit)】:
1、题目大意

n个站台 m个乘客 每个乘客都有起点和终点 车内限一人(也就是说送完这个才能送下一个)

求把起始(列车初始停靠的站台)建在哪里,使得总距离最小(如果相同,输出编号小的)

2、比赛中的思考

同理,暴力枚举,理应得50

我从1~n各枚举一遍,用数组计算距离

不出意外爆了....(T L E)

3、解题思路

前缀和+差分。(冷门知识焕发新生机)

首先我们先把必须走的路求出来(乘客的起点和终点不会变)

然后反手一个递推(等差数列,距一个人的距离*2 = 走的冤枉路)

最后打擂台求出最小值即可

4、AC代码

#include<iostream>
using namespace std;
int a,b,pos,n,m;
const int N = 500010;
long long pre[N],pree[N],suf[N],suff[N];
long long ans = 0x3f3f3f3f3f3f3f3f,sum;
int main(){
    cin>>n>>m;
    while(m--){
        cin>>a>>b;
        pre[a-1]+=2;
        suf[b+1]+=2;
        sum+=(b-a)*2;
    }
    for(int i = n;i>=1;i--){
        pre[i]+=pre[i+1];
        pree[i] = pree[i+1]+pre[i];
    }
    for(int i = 1;i<=n;i++){
        suf[i]+=suf[i-1];
        suff[i] = suff[i-1]+suf[i];
        if(suff[i]+pree[i]<ans){
            pos = i;
            ans = pree[i]+suff[i];
        }
    }
    cout<<pos<<" "<<sum+ans<<endl;
    return 0;
}

T4【异或和(exclusive)】:
1、题目大意

给定几个元素和ta们所处的集合以及能取的元素个数

求ta们的最大异或和

(解释:对于单个集合,ta的异或和 = 所有选取的元素按位异或;对于所有集合,ta们的异或和 = 所有集合的异或和相加<可以不是最大>,保证最终相加后的异或和最大)

2、比赛中的思考

可以说是一点思路没有,连模拟都做不到(组合数量太多了实在不好写,而且必爆)

最后也只能抄上样例骗骗分了,也没骗到.....

3、解题思路

感觉最后一题都是dp......

这一题我们遍历每个集合时,需要用动规把(构成这个数所需的最小元素个数)求出来,使用二维数组进行存储(dp[集合名称][这个值] = 构成这个值所需最少元素个数)

然后同等元素个数取值最大,最后加起来输出即可

说实话这一题我听完讲解后感觉还是绕....

正解居然还用了动态数组......

4、AC代码

#include<iostream>
#include<vector>
using namespace std;
const int N = 2100;
int dp[N][N],num[N][N],ans[N],si[N],dpp[N];
vector<int>v[N];
//dp 表示当前第I个数异或出j最少需要的数字个数
//num 表示第i组第J个数字的最大异或值
int n,m,a,b;
int main(){
    cin>>n>>m;
    for(int i = 1;i<=n;i++){
        cin>>a>>b;
        v[b].push_back(a);//a数字属于b组
        si[b]++;//b组数字+1
    }
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=2047;j++){
            dp[i][j] = 1e9;//标记
        }
    }
    for(int k = 1;k<=2000;k++){//k表示组数
        if(si[k]!=0){
            dp[1][v[k][0]] = 1;//初始化,第K组第一个数一定能组成自己
        }
        for(int i = 2;i<=si[k];i++){//遍历k组所有的数
            dp[i][v[k][i-1]] = 1;//初始化
            for(int j = 1;j<=2047;j++){
                if(dp[i-1][j]!=1e9){
                    dp[i][j] = min(dp[i][j],dp[i-1][j]);
                    dp[i][j^v[k][i-1]] = min(dp[i-1][j]+1,dp[i][j^v[k][i-1]]);
                    //看新数j^v[k][i-1]组成数字数量能否更新
                }
            }
        }//处理完第k组
        for(int j = 1;j<=2047;j++){
            if(dp[si[k]][j]!=1e9){//如果第k组的数可以产出j
                num[k][dp[si[k]][j]] = max(num[k][dp[si[k]][j]],j);
                //那么第k组使用dp[si[k][j]]个数字的最大异或值
            }
        }
        for(int i = 1;i<=si[k];i++){
            for(int j = 1;j<=2047;j++){
                dp[i][j] = 1e9;
            }
        }
    }
    for(int i = 1;i<=2000;i++){//遍历组
        for(int j = m;j>=1;j--){//最多使用m个数字
            for(int k = 1;k<=si[i];k++){//每组数字个数限制
                if(j>=k){//可更新,还有剩余次数
                    dpp[j] = max(dpp[j],dpp[j-k]+num[i][k]);
                    //在第i组中,使用j个数字的最大值,要么不更新,要么被
                    //在第i组中选j-k个数字形成的最大值+之前i组中选k个数字形成的最大值更新
                }
            }
        }
    }
    cout<<dpp[m]<<endl;
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值