日期 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分。