CSP-J 第二轮 模拟赛二补题报告

日期:2023年10月2日 星期一

学号:s07246

 姓名:江守栋

目录

· 比赛概况

· 比赛过程

· 题目分析

T1【人员借调 transfer】

1、题目大意

2、比赛中的思考

3、解题思路

4、AC代码

T2【计算 calc】

1、题目大意

2、比赛中的思考

3、解题思路

4、AC代码

T3【智能公交transit】

1、题目大意

2、比赛中的思考

3、解题思路

4、AC代码

T4【异或和exclusive】

1、题目大意

2、比赛中的思考

3、解题思路

4、AC代码

· 赛后总结


· 比赛概况

        总分:90分

        T1【人员借调 transfer】70

        T2【计算 calc】0

        T3【智能公交 transit】20

        T4【异或和 exclusive】0

· 比赛过程

今天的比赛比昨天难一些,按照顺序做的。

T1直接算的样例,用贪心,但少想了一种>240的情况,痛失30分。

T2没有想到可以用打表做,直接打了暴力,感觉能拿几分,但是忘记清空ans了,再次痛失30分。

T3我这种蒟蒻根本找不到思路,也是直接暴力,拿到20分

T4题干较短,以为是一道能秒过的题,但越做越迷糊,发现很难,最后只能输出样例 (QwQ)

· 题目分析

T1【人员借调 transfer】

1、题目大意

小可在AB两地往返,要去B地按顺序处理n件事,小可处理第 i 件要  min,但是当小可在B地连续待大于等于240min时,小可回来后必须在 A 地滞留10080 min ,然后才能继续活动。

现在他准备从 A 地出发,需要在 B 地处理完所有事,然后回到 A 地正常的工作。

求小可至少需要多少分钟

2、比赛中的思考

读题之后发现是贪心,但是因为 if 的判断条件写错了,卡了我将近40分钟才写完,少写了一种情况,痛失30分。
 

3、解题思路

一道贪心的题目,边输入边累加,无论去B地做多少事,都要返回A地,所以一定会产生一个往返的花费400

一个事情的完成情况有两种:

        1:所有事情的完成总时间<240,在B一次性处理完,则只需计算总时间和一次往返。

        2:所有事情的完成总时间>=240,则可能要做完后在会A滞留,又有两种情况:

                ①所有事情一次性在B做完,那么一定需要在A滞留7天,

                    总花费 = 总时间 + 滞留时间 + 一次往返

                ②连续做多件事情会超过240,则做完当前的事情后回A再回B,再继续做后面的事情

                    总花费=总时间 + 临时往返此时 * 400 + 固定往返400

              所以2这种情况只需累加后取min( ① , ② )即可

4、AC代码

#include<bits/stdc++.h>
using namespace std;
int n,x,ans=400,cnt=0,sum=0;
int main()
{
	//freopen("transfer.in","r",stdin);
	//freopen("transfer.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x;
		sum+=x;
		ans+=x;
		if(sum>=240){
			cnt++;
			sum=x;
		}
	}
	if(cnt>=1){
		if(n==1) ans+=10080;
		else ans=min(ans+10080,ans+cnt*400);
	}
	cout<<ans;
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

T2【计算 calc】

1、题目大意

在 m 与 n 这个区间内寻找一个 x ,使 x 在十进制下所有数位上的数字和等于 k ,输出 x 在十进制下所有位上的数字之积最大的那个(如果有多个积相等,则输出x最小的那个)并且保证有解

2、比赛中的思考

读懂题后尝试朴素的for循环暴力,想着骗点分


int T,n,m,k,x,sum=0,cnt=1,maxx=0,ans;
void baoli(){
	for(int i=m;i<=n;i++){
		x=i,sum=0,cnt=1;
		while(x!=0){
			sum+=(x%10);
			cnt*=(x%10);
			x/=10;
		}
		if(sum==k&&cnt>maxx){
			maxx=cnt;
			ans=i;
		}
	}
}
int main()
{
	freopen("calc.in","r",stdin);
	freopen("calc.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&m,&n,&k);
		baoli();
		printf("%d %d\n",ans,maxx);
		cout<<endl;
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

但是这段代码0分,原因是多组输入,忘记清空ans和maxx了 这辈子不可能再犯这种错了,痛失30分。。。

3、解题思路

看眼数据,

在30%数据下:  n \leq 1000,n-m\leq 1000
另有20%数据: n \leq 2*10^5 ,n-m=0
另有20%数据: n \leq 2*10^5 ,n-m\leq2*10^5
在100%数据下:1\leq m\leq n \leq 5*10^6 , 1\leq k\leq 1001 \leq T\leq 100

(这个样例输入也是十分的好看呢

暴力顶多30分,如果每次查询的时间复杂度是O\left ( N \right ),超时,那该如何完成这道题呢?

很久以前,一位著名的OIer说过一句话:“暴力出奇迹,打表出省一”,所以,这道题直接使用美妙的打表。

根据数据范围,我们可以算出 1~5e5 范围所有数字各位之和与各位之积,再通过查表输出,完美AC,奉上AC代码

4、AC代码

int T,n,m,k,x,sum=0,cnt=1,maxx=0,ans;
int a[5000005],c[5000005];
int main()
{
	//freopen("calc.in","r",stdin);
	//freopen("calc.out","w",stdout);
	c[0]=1;
	for(int i=1;i<=5000000;i++){
		a[i]=a[i/10]+(i%10);
		c[i]=c[i/10]*(i%10);
	}
	c[0]=-1;
	int T,ans;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&m,&n,&k);
		for(int i=m;i<=n;i++){
			if(a[i]==k&&c[i]>c[ans]){
				ans=i;
			}
		}
		cout<<ans<<' '<<c[ans]<<endl;
	}
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

T3【智能公交transit】

1、题目大意

共有n个公交站台,一辆公交车在 n 个站台之间穿梭。如果公交上没有乘客,那么公交就会停在 x   站台。只要有人坐公交,公交车就会从 x  赶到出发地,把他送到目的地,接着回到 x ,然后继续载下一个人,计算出 x ,使公交移动距离最短。

2、比赛中的思考

直接打暴力,枚举每个点,拿到20分

3、解题思路

本题正解是用前缀和与差分,重点在于前缀和与差分互为逆运算

  • a数组中,每个数字后面减前面得到的数字填入b数组,b数组就叫a数组的差分数组。
  • 同时,a数组就是b数组的前缀和数组。
  • 通过“叠加“差分数组,就可以还原出“原数组”的每一个数字。
  • 前缀和与差分互为逆运算,有原数组可以计算出差分数组;有差分数组,也可以还原成原数组。

先在处理一遍差分的前缀和,求出每个点到第i个点多走的路程,然后再求一遍就是多走的路程的和啦。

求出来之后遍历取最小值即可。

4、AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
long long a[N],b[N],a1[N],b1[N];
int main()
{
	long long sum=0,n,m,aa,bb,ans=1e18+10,anss;
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	while(m--){
		cin>>aa>>bb;
		a[aa-1]+=2;
		b[bb+1]+=2;
		sum+=(bb-aa)*2;
	}
	for(int i=n;i>=1;i--){
		a[i]+=a[i+1];
		a1[i]=a[i]+a1[i+1];
	}
	for(int i=1;i<=n;i++){
		b[i]+=b[i-1];
		b1[i]=b[i]+b1[i-1];
		if(b1[i]+a1[i]<ans){
			anss=i;
			ans=b1[i]+a1[i];
		}
	}
	cout<<anss<<' '<<ans+sum<<endl;
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

T4【异或和exclusive】


1、题目大意

多个集合中总共有n个数字,并且已知每个数字的大小ai和属于某个集合bi。在一个集合中选择一个数字,收益为这个数字的大小,选择多个数字,收益为这些数字的异或和。

总收益为每个集合的收益之和。最多从中选择m个数字,使这些数字总收益最大。

2、比赛中的思考

怎么说呢,本人实力太蒻,算法什么的想都没想,直接暴力,没想到暴力也写得不太对,只能万能cout了。。。

3、解题思路

采用dp,如果采用三维DP,2000的数据会爆掉,所以压成二维,滚动使用。

dp[i][j]表示前 i 个数字想要异或出 j 需要的最少次数

num[k][i]表示 k 集合中 i 个数的最大异或值。

遍历所有集合和所有数字,如果被选了,就看看是否能更新,状态转移方程:

dp[i][j]=min(dp[i][j],dp[i-1][j]);

dp[i][j ^ve[zu][i-1]]=min(dp[i-1][j]+1,dp[i][j^ve[zu][i-1]]);

最后再用一个分组背包,加上 m 个数据的限制,遍历一遍,求个 max

dpp[j]=max(dpp[j],dpp[j-k]+num[i][k]);

这道题怎么说呢

我这种小蒟蒻,做此题之难,难于上青天

4、AC代码

#include <bits/stdc++.h>
using namespace std;
int n, m, dp[2005][2050], num[2005][2005], dpp[2050], zz[2005];
// dp[i][j]表示看到前i个数,得到收益j至少所需要的数字个数,num[i][j]表示第i组j个数最大的收益值
vector<int> ve[2005];
int main() {
	cin >> n >> m;//n个数字,最多选m个数字 
	for (int i = 1; i <= n; i++) {//输入n个数字 
		int x, y;//x是数字大小,y是所属集合编号 
		cin >> x >> y;
		ve[y].push_back(x);//y集合的vector进入一个x 
		zz[y]++;//集合y的元素个数加一个 
	}
	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 (zz[zu] != 0)//如果这一组的元素不为空 	
			dp[1][ve[zu][0]] = 1; //第一个数 得到这一组的第一个数的收益值通过选择这一个数做到
			  
		for (int i = 2; i <= zz[zu]; i++) {//遍历这一组剩下的数字 
		// 前i个数,得到这个组的第i个数的收益可以通过直接选择这个数本身做到 
			dp[i][ve[zu][i - 1]] = 1; 
			for (int j = 1; j <= 2047; j++) {//遍历所有可能的数字(收益值) 
				if (dp[i - 1][j] != 1e9) {//如果前i-1个数字产生这个收益所用的最小数字个数存在(不为初始值) 
				
				//前i个数产生j这个数字(收益)的数字应用个数是前i个数和前i-1个数的使用数字个数的最小值 
					dp[i][j] = min(dp[i][j], dp[i - 1][j]);
				
				//前i个数字产生j^这一组的第i个数字产生的新数字(收益)的数字使用个数是产生j的数字个数+1,和本来就有的数字个数的最小值 
					dp[i][j ^ ve[zu][i - 1]] =min(dp[i - 1][j] + 1, dp[i][j ^ ve[zu][i - 1]]);
				}
			}
		}
		
		for (int j = 1; j <= 2047; j++) {//遍历所有的收益 
			if (dp[zz[zu]][j] != 1e9)//如果这一组的数字个数对应产生j这个数字(收益),所使用的最小数字个数存在,即能异或出这个数字 
			//num数组记录这一组对应 这些数字个数 能得到的最大数字 
				num[zu][dp[zz[zu]][j]] = j;
				
		}
		for (int i = 1; i <= zz[zu]; i++) {//dp数组重新初始化一下 
			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 <= zz[i];k++) { // 最后的枚举,只枚举到本组的个数
				if (j >= k)//能选这个组的k个数字
				//j个数字产生的数字收益最大值要么不变,要么就是往前推k个数,从第i组选k个数字的最大收益累加 
					dpp[j] = max(dpp[j], dpp[j - k] + num[i][k]);
			}
		}
	}
	cout << dpp[m];//输出m个数字的最大收益 
	return 0;
}

· 赛后总结

今天的题目比昨天的要难一些,但我只考90分也是万万不应该的,

第一题完全应该再仔细一些,拿到满分。

第二题我是冲着部分暴力分去的,但是一分也没拿到的原因也是粗心,非常不应该

第三题,考试时没有想到正确的方法,其实二分也可以做这道题,但我还是走向了暴力这条路

最后一题,完全没有头绪,甚至暴力都不会写。

总的来说,这次的模拟赛反映出了我粗心的问题,第三第四题也让我认识到了自己代码能力的不足,应该加强平时题目的练习,明天的比赛再接再厉!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值