CSP-J模拟赛(补题)- 5

日期 2023年10月5日星期四

学号 S11142

一、比赛概况

总分400分,拿到150分,其中第1题100分,2题30分,3题20分,4题0分,赛后补题:全部正确。

二、比赛过程 

第一题较简单,很快做出来。第二题忽略了特殊情况导致错误。第三题没有做对,暴力得了20分。第四题没有思路。

三、解题报告

1.重复判断

(1)题目大意

判断字符串a是否是字符串b重复若干次得到的。

(2)比赛中的思考

很快想出解决办法,解决。

(3)解题思路

将b字符串重复若干次,看看是否能得到a字符串。

(4)AC代码
#include<iostream>
#include<cstdio>
using namespace std;
int t;
string a,b;
string s;
int main(){
	cin>>t;
	while(t--){
		cin>>a>>b;
		s="";
		while(s.size()<a.size()){
			s+=b;
		}
		if(s==a){
			cout<<"YES\n";
		}
		else cout<<"NO\n";
	}
	return 0;
}

 2.歪果仁学乘法

(1)题目大意

给定两个正整数a,b。对于a × b:
1. 将a,b的每一位上的数码画成线,不同位之间分隔开。 
2. a 和 b 的方向垂直画出。
3. 数出每个方向上交点的个数,即是 c 对应位置上的数码。
样例图给出了计算12 ×13的方法:
1. 红色线分别画出 1 条和 2 条;
2. 蓝色线分别画出 1 条和 3 条; 
3. 数出红、蓝色线的交点个数,依次为 1,5,6 个;
4. 得到答案:12 × 13 = 156。
给出两个数字 a, b,求它们的乘积时交点的总个数是多少。 

注意:1≤a,b≤99。

(2)比赛中的思考

没有考虑进位的情况,直接算了乘机的数位之和,导致答案错误。

(3)解题思路

个位*个位+个位*十位+十位*个位+十位*十位。

(4)AC代码
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
ll n,m;
int main(){
	scanf("%lld %lld",&n,&m);
    //个位*个位+个位*十位+十位*个位+十位*十位
	printf("%lld",(n%10*(m%10))+(n%10*(m/10))+
	(n/10*(m%10))+(n/10)*(m/10));
	return 0;
}

3.去重求和

(1)题目大意

小可有一个长度为 n 的序列 。
他定义sum(l,r),为a[l]~a[r] ,这些数去重之后的和。
请求出sum(1,1)+sum(1+2)+...+sum(1,n)+sum(2,1)+sum(2,2)+...+sum(n,n)。

答案对1e9+7取模。

(2)比赛中的思考

先写出了暴力解法。

后来没有做出其他做法,就拿了20分。

(3)解题思路

用map标记出现了的数字。

计算出每个数字对答案的贡献,出现的次数,和数字本身的值相乘即可。

首先进行公式推导:

假如我们的序列为 [ 1, 2, 3, 2, 4 ] 。

计算答案的过程为:

计算 a[1] 为开头的和:

  sum(1, 1) + sum(1, 2) + sum(1, 3) + sum(1, 4) + sum(1, 5)

=a[1] + a[1] + a[2] + ... + a[1] + a[2] + a[3] + a[5] 

=a[1]*5 + a[2]*4 + a[3]*3 + a[5]*1

我们发现没有 a[4] 项(去重)

同理以 a[2] 为开头的和:

a[2]*4 + a[3]*3 + a[5]*1

还是没有 a[4] 项

继续:

a[3]*3 + a[4]*2 + a[5]*1

这里有 a[4] 项了,但是 a[2] 项没有了。

我们发现,计算以 a[i] 为开头的和时,如果有重复的数字,只会累加最先出现的那个。

那么我们得出公式:

sum[i] = (n-i+1) * (i-pos) * a[i]

其中pos是当前数字上一次出现的位置,用i减去pos就是当前数字需要计算的次数,即上文中的列数。n-i+1 则是每一横行的次数。相乘即为每个数字需要相加的总次数,乘以数字本身 a[i] ,就是这个数字对答案的贡献。

累加每个数字对答案的贡献即可。

(4)AC代码
#include<iostream>
#include<cstdio>
#include<map>
#define int long long
using namespace std; 
const int MOD=1e9+7;
int n;
int ans;
int a[500005];
map<int,int> mp;
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",a+i);
		mp[a[i]]=0;//初始化mp
	}
	for(int i=1;i<=n;i++){
		ans+=(n-i+1)*a[i]%MOD*(i-mp[a[i]]);//数字本身*出现的次数(去重)
		ans%=MOD;
		mp[a[i]]=i;//记录当前数字最近出现的位置
	}
	printf("%lld",ans);
	return 0;
}

4.点集操作

(1)题目大意

小可在学习图论,他有一个有向无环图,他想知道对这个图做任意次 modify(i,j) 操作之后的图中剩余的最小点数,其中1≤ i,j≤n,其中 modify(i,j) 为一次操作:
1. 任选不同的两个点 i , j。
2. 称 Ai 为 i 能到达的所有点组成的点集,Aj 为 j 能到达的所有点组成的点集 。(注意:每个点可
以到达的点集包含这个点本身)。
3. 设 B 为一个最大的点集,满足 B 既是 Ai 的子集,又是 Aj 的子集 。
4. 将 B 在图中变成一个新点,B 内的所有边全部删除。点集 B 以外的点与点集 B 以内的点的连边关系转移到新点上。

(2)比赛中的思考

尝试写暴力,但是链式前向星删除边的操作不会写,就没做出来。

(3)解题思路

如果给定的图是一条链,那么它经过最多次操作后只会剩下开头的点和第二个点。所以我们只需要找到一些链,按照上述的方式计算即可。

(4)AC代码
#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
using namespace std;
int n,m;
vector<vector<int> > a;
int vis[1000005],ind[1000005];
signed main(){
	cin>>n>>m;
	int x,y;
	a.resize(n+1);//设置vector数组长度
	for(int i=1;i<=m;i++){
		cin>>x>>y;
		ind[y]++;//统计入度
		a[x].push_back(y);//加边
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(ind[i]){//如果当前点的入度大于0,说明可以被其它点到达
			for(int j=0;j<a[i].size();j++){
				vis[a[i][j]]=1;//删除这个点的所有邻接点
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			ans++;//统计没有被删去的点的个数
		}
	} 
	cout<<ans<<endl;
	return 0;
}

总结:

第二题没有审好题,丢了70分,有点亏。第三题暴力写的太麻烦,正常暴力可以得50分。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值