CSP-J复赛模拟赛后补题报告Day2

日期:2023年10月3日星期二

S10698

1.比赛概况

比赛共有4题,满分400分,赛时拿到80分,其中第一题30分,第二题爆零,第三题50分,第四题爆零。

2.比赛过程

首先浏览了一遍题目,然后先做的第一题,第一题就是一个简单的暴力模拟题,分析好各种情况就可以,但是我在做题的时候把问题想复杂了,分了很多种情况讨论,导致花费了很长的时间,最后只拿到了30分。第三题是一个用贪心或者DP来解决的问题,当时没有想到怎么做就用了一个暴力的方法来做,但也花费了较长的时间,得到了50分。看第四题的时候,发现是一个位运算和分组背包结合的问题,当时忘了异或运算怎么进行了,所以就根据10%的数据进行了骗分,但这道题最后依然爆零了。第二题浏览的时候感觉很复杂,就跳过了,结果到最后的时候没有时间了,就根据10%的数据进行了骗分,但是跟第四题一样,最后也爆零了。

3.题解报告

(1)第一题:人员借调(transfer)

情况:赛中30分,已补题。
题意: 小可的工作能力很突出,导致B地的领导想要借调小可,但是A地的领导对借调这件事很不赞同,如果小可在B地工作超过了240min,那么他就会在A地禁足一周(10080min),然后才能选择继续去B地或者留在A地,小可需要在 B 地处理完所有事,然后回到 A 地正常的工作而且在B地的工作顺序不能打乱,往返AB两地需要400min,求小可至少需要多少分钟

赛时本题做题想法:

题解:先分两种情况讨论:①小可在B地有一个大于等于240min的工作,他进行完所有工作和进行完这个240min工作回去,都要受到一周的禁足,那么干脆破罐子破摔,在B地进行完所有工作再回去。②所有的工作都没有超过240min且总时间不超过240min,然后计算一次往返或者一次性在B地全部处理完,若时间超过240min,计算一下禁足一周的时间,然后再计算一次往返400min的时间。

AC代码:

#include <iostream>
#include <cstdio>
using namespace std;
int n,a[1010],sum[1010],flag,res1,res2,res;
int main() 
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
		if(a[i]>=240)
		{
			flag=1;
		}	
	}
	if(flag==1)
	{
		res=sum[n]+400+7*24*60;
	}	
	else 
	{
		res1=sum[n]+400+7*24*60;
		int l=0,res2=sum[n];
		for (int r=1;r<=n;r++) 
		{
			if(sum[r]-sum[l]>=240) 
			{
				l=r-1;
				res2+=400;
				r--;
			} 
			else if(r==n&&sum[r]-sum[l]<240) 
			{
				res2+=400;
			}
		}
		res=min(res1,res2);
	}
	printf("%d",res);
	return 0;
}

(2)第二题:计算(calc)

情况:赛中爆零,已补题。
题意: 让我根据一下条件在m到n的范围中找到一个数x。

1:x在m、n之间

2:x 在十进制下所有位上的数字之和为 k

根据计算条件得到的 x可能会很多,请输出十进制下所有位上的数字的乘积最大的那个(如果有多个积相等,则输出 x 最小的那个)

赛时本题做题想法:时间不够了,就没多想,直接根据数据骗分。

题解:因为这个题的数据范围只有五乘十的六次方,数据比较小,我们可以直接进行打表,然后再依次查m到n的范围中数字的表,如果当前这个数符合各位相加等于k,并且乘积最大,那么我们就标记它的下标。最后输出它的位置和各位的乘积。

AC代码:

#include <iostream>
#include <cstdio>
using namespace std;
int e[5000005],pi[5000005];
int t,m,n,k,ans;
int main() 
{
	pi[0]=1;
	for(int i=1;i<=5000000;i++) 
	{
		e[i]=e[i/10]+(i % 10);
		pi[i]=pi[i/10]*(i % 10);
	}
	pi[0]=-1;
	scanf("%d",&t);
	for(int i=1;i<=t;i++)
	{
		ans=0;
		scanf("%d %d %d",&m,&n,&k);
		for(int i=m;i<=n;i++) 
		{
			if(e[i]==k&&pi[i]>pi[ans]) 
			{
				ans=i;
			}
		}
		printf("%d %d\n",ans,pi[ans]);
	}
	return 0;
}

(3)第三题:智能公交(transit)

情况:赛中50分,已补题。
题意: 一个公路上有n个公交站牌,有m个乘客需要乘坐智能公交,智能公交在每一次送完乘客之后都会返回位置x,然后问你智能公交在哪个位置时移动的距离才会最短。

赛时本题做题想法:当时想的可能是贪心或者DP,但是都没有想到怎么去做,就用了暴力,把1-n每一个位置送完乘客的距离都算了一遍,最后取了一个最小值。因为有大数据,所以这个暴力的解法只得了50分。

题解:智能公交要是想得到最短距离,只能是在中间的位置。我们用一个累加器来记录距离,先将区间的距离累加,随后累加其余往返的距离,最终得到最短距离。
AC代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
const int N=5e5+10;
int n,m,x,y;
int a[N*2],cnt;
int main() 
{
	long long sum=0;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		sum+=y-x;
		a[++cnt]=x;
		a[++cnt]=y;
	}
	sort(a+1,a+1+cnt);
	long long pos=cnt/2;
	for(int i=1;i<=cnt;i++)
	{
		sum+=abs(a[pos]-a[i]);
	}
	printf("%d %lld",a[pos],sum);
	return 0;
}

(4)第四题:异或和(exclusive)

情况:赛中爆零,已补题。
题意: 

赛时本题做题想法:想到了位运算和组合背包的结合,但没有推出动态转移方程,所以就根据数据骗分了。

题解:dp数字求最少的数字,就是因为一共选m个,所以用最少的数字得到最大的收益,才能保证m个选的多,然后和才大,类似于背包问题,拿的东西又轻又贵才好。然后num数组是把每一组,拿多少个数字,能产生的那个最大收益都给他记录下来,然后等着一会变成背包问题,挨个拿就行了

 AC代码:

#include <iostream>
#include <vector>
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 zu = 1; zu <= 2000; zu++) {//遍历所有的组 ,一组一组的处理
		for (int i = 1; i <= zz[zu]; i++) {//dp数组重新初始化一下
			for (int j = 1; j <= 2047; j++) {
				dp[i][j] = 1e9;
			}
		}
		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 <= 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个数字的最大收益
}

4.总结

①加强DP和贪心的运用,背诵一下DP的模板。
②加强对题目的理解和分析,可以知道题目考的是哪方面的知识。
③不要简单问题复杂化。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值