日期:2023年10月2日 星期一
学号:s07246
姓名:江守栋
目录
· 比赛概况
总分: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、题目大意
在 与 这个区间内寻找一个 ,使 在十进制下所有数位上的数字和等于 ,输出 在十进制下所有位上的数字之积最大的那个(如果有多个积相等,则输出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%数据下:
另有20%数据: ,
另有20%数据: ,
在100%数据下: , ,
(这个样例输入也是十分的好看呢)
暴力顶多30分,如果每次查询的时间复杂度是,超时,那该如何完成这道题呢?
很久以前,一位著名的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的数据会爆掉,所以压成二维,滚动使用。
表示前 i 个数字想要异或出 j 需要的最少次数
表示 k 集合中 i 个数的最大异或值。
遍历所有集合和所有数字,如果被选了,就看看是否能更新,状态转移方程:
最后再用一个分组背包,加上 m 个数据的限制,遍历一遍,求个 max
这道题怎么说呢
我这种小蒟蒻,做此题之难,难于上青天
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分也是万万不应该的,
第一题完全应该再仔细一些,拿到满分。
第二题我是冲着部分暴力分去的,但是一分也没拿到的原因也是粗心,非常不应该
第三题,考试时没有想到正确的方法,其实二分也可以做这道题,但我还是走向了暴力这条路
最后一题,完全没有头绪,甚至暴力都不会写。
总的来说,这次的模拟赛反映出了我粗心的问题,第三第四题也让我认识到了自己代码能力的不足,应该加强平时题目的练习,明天的比赛再接再厉!