2023CSP-J复赛冲刺模拟赛二赛后补题报告(learn.coduck.cn)


1.比赛概况:

        比赛共4题,得了140/400,T1-T4得分70/20/50/0


2.比赛过程:


    今天的比赛,比昨天难了一(亿)点,但我得分竟然比昨天高!

     T1一看题就有了思路,直接写了,(结果是个分类讨论,真服了!)

     T2一看题就把从m到n遍历排除(结果这竟然是正解!),又想把k(≤100)分离,跑一个搜索,加上亿点剪枝,还写了个对拍,确定对了(结果TLE了,还没别人暴力的分多 QwQ~~)

     T3一看题,有印象!是个贪心。一写,懵了。赶紧画图,又画懵了。最后写了一个暴力。(结果思路没错,只是我把两个数组和一起,没法用了)

     T4很难。脑子里只有一个背包模板,使劲往上套,最后写了个奇怪的东西。


3.题解报告:


                              T1:人员借调 (transfer.cpp)


         情况: 70分,已补题。


         题意:小可处理n件事情,处理第 i件的耗时为 a​i​​ 分钟。正常的过程为小可从 A 地到 B 地进行处理,处理结束之后回到 A 地。但是如果小可在 B 地待连续大于等于240分钟时,会强制把小可留在 A 地 7 天( 10080 分钟),再继续工作。小可有一个对策,在 240 分钟快到的时候就此 B 地回到 A 地,然后再去 B 地,这样的话 240分钟就会重新计时。注意:往返一次耗时 400 分钟(不计算待在 B 地的时间)。现在小可从 A 地准备出发,需要在 B 地处理完所有事,然后回到 A 地正常的工作。注意:事情不可以打乱,并且处理事情必须处理完才可以继续后面的内容。请问小可至少需要多少分钟?

         数据范围:

        在 20% 数据下:n=1

        在 40% 数据下:1≤n≤2

        另有 10% 数据:有 a​i​​≥240

        对于 100% 数据:有 1≤n,a​i​​≤1000


         赛时本题做题想法:把数据划为小于240的几组,再计算时间。如果有≥240的,与前面的合为一组,再让时间+10080.注意无论如何都有一次往返。


         题解:有两种情况:1.处理事情且总时间不超过240,计算一次往返。2.一次性全部处理完。又分两种情况:(1)总时间超过240,计算一下滞留7天时间(2)计算去了B地之后,某一件事情办完会被留在A地7天,选择在B地全部办完所有事情。注意:前缀和优化。


        AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
int n,ans=0,p=0,x,cnt=0;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x;
        ans+=x;
        p+=x;
        if(p>=240){
            cnt++;
            p=x;
        }
    }
    if(cnt){
        if(n==1) ans+=10080;
        else ans+=min(10080,cnt*400);
    }
    cout<<ans+400;
    return 0;
}


                                   T2:计算(calc.cpp)


         情况:20分,已补题。


         题意:有三个整数 m,n,k.(设x为满足计算条件的数字):1:m≤x≤n   2:x在十进制下所有位上的数字之和为k。 x会很多,输出十进制下所有位上的数字的积最大的x(如果有多个积相等,输出最小的x)保证有解。

         数据范围:

        在 30% 数据下:n≤1000,n−m≤1000

        另有20%数据:n≤2×10​5​​,n−m=0

        另有20%数据:n≤2×10​^5​​,n−m≤2×10^​5​​

        在100%数据下:1≤m≤n≤5×10^​6​​,1≤k≤100,1≤T≤100

         赛时本题做题想法:先把从m到n遍历排除,然后搜索,把k分成a份(a≤7)(0≤每一份≤9),再进行剪枝。


         题解:空间换时间,打表处理1~5*10^6的各个数位的和与积。再从m到n遍历。


         AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
int a[5000005],b[5000005]={1},t,n,m,k;
int main(){
    for(int i=1;i<=5000000;i++){
        a[i]=a[i/10]+(i%10);
        b[i]=b[i/10]*(i%10);
    }
    b[0]=-1;
    cin>>t;
    while(t--){
        cin>>m>>n>>k;
        int ans=0;
        for(int i=m;i<=n;i++){
            if(a[i]==k&&b[i]>b[ans]) ans=i;
        }
        cout<<ans<<" "<<b[ans]<<endl;
    }
    return 0;
}


                                 T3:智能公交(transit.cpp)


        情况:50分,已补题。

        题意:共有n个公交站台(1,2,⋯,n)有一辆智能公交车会在这 n个站台之间穿梭。如果智能公交上没有乘客,会停靠在x站台。有人按动公交站台上的按钮,智能公交就会快到到达相应的站台,随后智能公交把人送到目的地,又回到x站台。现在有m个人要依次乘坐智能公交,每个人都会等待智能公交停在x站台之后在按动当前站台按钮准备乘坐公交。现在已知第i个人都是从a站台到b站台。计算x使得智能公交移动距离最短。最终输出x和最短的距离x若有多个,输出最小的一个。

        数据范围

        对于10%数据:1≤n,m≤100

        对于50%数据:1≤n,m≤2000

        另有20%数据:对于任意的i(1≤i<m) 有a​i​​<b​i​​<a​i+1​​<b​i+1​​

        对于100%数据:1≤n,m≤5×10^5​​,1≤a,b≤n

         赛时本题做题想法:用贪心来判断每一个站台公交要跑的最短距离,利用差分来维护左右两侧公交起始与终点的数量。结果,想法很好,写不出来。最后打了个暴力。

        题解:有点像个数学题。公交车从站台a到站台b,且停靠位置x在a到b之间,那么移动距离为:|x-a|+|a-b|+|x-b|=2|a-b| 。在这之外,每向外一个站台公交车多移动的距离就+2(因为往返*2),公交车多移动的距离呈公差为2的等差数列。假设f(x)表示公交车停靠在 的总移动距离,那么给定a,b的时候,相当于将整个数组全部加2|a-b|,并且将a-1到1的位置额外加一个公差为 2 的等差数列;将b+1到n 的位置加一个公差为2的等差数列。所以用差分、前缀和来维护区间加法和等差数列加法。

        AC代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long 
#define N 500005
using namespace std;
ll n,m,a,b,ans=0,l[N],r[N],sl[N],sr[N],sum=0x3f3f3f3f3f3f3f3f,p;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        l[a-1]+=2;
        r[b+1]+=2;
        ans+=2*(b-a);
    }
    for(int i=n;i>=1;i--){
        l[i]+=l[i+1];
        sl[i]=sl[i+1]+l[i];
    }
    for(int i=1;i<=n;i++){
        r[i]+=r[i-1];
        sr[i]=sr[i-1]+r[i];
        if(sl[i]+sr[i]<sum){
            sum=sl[i]+sr[i];
            p=i;
        }
    }
    cout<<p<<" "<<ans+sum;
    return 0;
}


                                    ​​​​​T4:异或和(exclusive.cpp)


        情况:0分,已补交。

        题意:总共有n个数字,每个数字的大小ai和属于某个集合 bi。在一个集合中选择一个数字,收益为这个数字的大小,选择多个数字,收益为这些数字的异或和。总收益为每个集合的收益之和。注意:最多从中选择m(m≤n)个数字,使这些数字总收益最大。

        数据范围:

        有10%数据:m=n,b​i​​=1,1≤n,a​i​​≤2000

        另有10%数据:1≤b​i​​≤2,1≤n,a​i​​≤2000

        另有30%数据:1≤b​i​​≤2000,1≤n,a​i​​≤200

        在100%数据下:1≤b​i​​≤2000,1≤n,a​i​​≤2000

         赛时本题做题想法:是个分组背包。然后……就没有然后了。这题的暴力我都写不出来,真服了!(但老师说这题是入门的极限了,基本不会考) (我第一次见到分组背包+3维压2维+答案给我都看不懂)。


         题解:略(无法用语言描述)

这题要对每一组进行预处理dp[i][j]表示某一组前i个数,凑成异或和为j的数的最少个数。状态转移方程为f[i][j] = min(f[i][j], f[i - 1][j])   f[i][j ^ \Lambda a[i]] = min(f[i - 1][j], f[i][j ^ \Lambda a[i]]);

因为数据≤2000,所以我们三维压二维,使用滚动数组,把每一组的结果存在sum中。sum[i][j]表示第i组选j个数最大的异或值.注意:要初始化,0x3f3f3f3f可能被卡住。

最后使用分组背包得到最大异或值。


         AC代码:

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,m,a,b,t,dp[2005][2050],num[2005][2005],dpp[2050],z[2005];
vector<int> v[2005];
int main(){
   	cin>>n>>m;
   	for(int i=1;i<=n;i++){
   		scanf("%d%d",&a,&b);
   		v[b].push_back(a);
   		z[b]++;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=2047;j++) dp[i][j]=1e9;
	}
	for(int zu=1;zu<=2000;zu++){
		if(z[zu]!=0) dp[1][v[zu][0]]=1;
		for(int i=2;i<=z[zu];i++){
			dp[i][v[zu][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[zu][i-1]]=min(dp[i][j^v[zu][i-1]],dp[i-1][j]+1);
				}
			}
		}
		for(int j=1;j<2047;j++){
			if(dp[z[zu]][j]!=1e9) num[zu][dp[z[zu]][j]]=j;
		}
		for(int i=1;i<=z[zu];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--){
   			for(int k=1;k<=z[i];k++){
   			 	if(j>=k) dpp[j]=max(dpp[j],dpp[j-k]+num[i][k]);
			}
		}
	}
	cout<<dpp[m];
	return 0;
}


4. 赛后总结:

        今天题很难(QwQ),做成这样不容易。但要注意几个问题:1.思路放宽一些(T2),多考虑一些情况(T1),(不然像我T1少了一部分,T2首先排除了正解) 2.注意细节,把思路理清楚(T3) 3.

        最后,希望明天简单一些。orz。
    

                                               日期:2023年10月01日星期天
                     
                                     姓名:董峻熙

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值