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

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

学号:S08683

姓名:王皓轩

1. 比赛概况

比赛总分共 4 题,满分 400,赛时拿到 80 分,其中第一题0分,第二题80分,第三题0分,第四题0分。

2. 比赛过程:

先做第一题用的map但是映射的方向错了,报0。

然后做第二题用一堆if,就得80分。

第三题做10分的样例,也没对。

第四题写了写,但没有思路。

3. 题解报告:

(1) 第一题:数字对应

情况:赛中0分,已补题

题意:输入一个n个数字的序列a,把序列a的数字对应成序列a没有的数字,输出序列。

数据范围:对于 100% 数据:1<=10^5,1<=ai<=10^9

赛时本题做题想法:数据范围太大,本来想用一个别的数组再存有或没有的情况,然后再把a数组和map对应,但是存有或没有时数组就超限了

题解:因为还要输出对应的数,必须先用一个数组存输入。然后用ma这个映射来存是否有数,相当于一个超大的桶。用t的映射把序列a中对应的数存进去。因为一对一,所以有了数就不能更新,直接输出。否则找到一个没有数的位置,标记为有数,然后存储,最后输出。

AC 代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[100005],last=1;
map<int,int> mp,t;
int main(){
  cin>>n;
  for(int i=1;i<=n;i++){
     cin>>a[i];
     mp[a[i]]++;
  }
  for(int i=1;i<=n;i++){
     if(t[a[i]]){
       cout<<t[a[i]]<<" ";
     }
     else{
       while(mp[last])last++;
       t[a[i]]=last;
       mp[last]++;
       cout<<last<<" ";
     }
  }
  return 0;

}

(2) 第二题:技能学习

情况:赛中80分,已补题。

题意:n个人,有m份学习资料,看k本才会生效,每人最多获得q个能力,一共t分钟,求如何让所有人总和最大。

赛时本题做题想法:用if对其判断如何每人全分到,然后把多的都给一个人,对数量进行判断。

题解:与我的想法差不多,还主要是多让每个人都能拿到,然后进行取余判断,但这里要再把多出来的尽可能平分给多人。简单来说,就是越分散越好。

AC 代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
using namespace std;
ll n,m,k,q,t,f1,n1,f2,n2,ans;
int main()
{
  cin>>n>>m>>k>>q>>t;
  if(m<k){
     cout<<0;
     return 0;
  }
  if(n*k>m)n=m/k;
  m-=n*k;
  if(m%n!=0){
     f2=k+m/n+1;
     n2=m%n;
  }
  f1=k+m/n;
  n1=n-n2;
  ans+=min(f1*t,q)*n1;
  ans+=min(f2*t,q)*n2;
  cout<<ans;
  return 0;
}

(3) 第三题:等于

情况:赛中0分,已补题。

题意:长度n的序列,每个元素必是(-2,-1,1,2)中一个。求多少个子数组最大值的绝对值等于最小值的绝对值。

赛时本题做题想法:发现如果都是一样的,数量就是1+2+3+···+n,是一条10分样例。然后发现数据范围也大,循环模拟也不行,直接输出n*(n+1)/2,求1加到n。但是文件操作把q都写成p,10分也没了。

题解:可以先在开始时数重复的,数量就是1加到长度。然后可以用桶统计从此下标开始往后的(-2,-1,1,2)这几个数,开始最大值防止算多。然后统计。

AC 代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5e5+10;
const int inf=0x3f3f3f3f;
ll n,ans,num[maxn];
ll  nxt[maxn][5];
int main()
{
   cin>>n;
   memset(nxt,0x3f,sizeof(nxt));
   for(int i=1;i<=n;i++)cin>>num[i];
   ll cnt=1,lst=num[1];
   for(int i=2;i<=n;i++){
     if(num[i]==lst)cnt++;
     else{
        ans+=cnt*(cnt+1)/2;
        cnt=1;
        lst=num[i];
     }
   }
   ans+=cnt*(cnt+1)/2;
   for(int i=n;i>=1;i--){
     for(int j=0;j<=4;j++){
        nxt[i][j]=nxt[i+1][j];
       
     }
     nxt[i][num[i]+2]=i;
    int maxpos1=nxt[i][3],maxpos2=nxt[i][4],minpos1=nxt[i][1],minpos2=nxt[i][0],startpos=max(maxpos2,minpos2),endpos=n+1;
     if(startpos!=inf&&startpos<endpos)ans+=endpos-startpos;
     startpos=max(maxpos1,minpos1);
     endpos=min(min(maxpos2,minpos2),(int)n+1);
     if(startpos!=inf&&startpos<endpos)ans+=endpos-startpos;
   }
   cout<<ans;
   return 0;
}

(4) 第四题:最小方差

情况:赛中0分。

题意:无根树T有n个点,n-1条边,找一个树根树确定后计算出树上每个点到 root的距离,得到一个长度为 n 的序列 a。请让序列a的方差最小。为了方便输出,输出的方差值乘以 n^2

提示:设序列 a 的平均值为 x,那么乘以 n^2后的方差为 n*×∑n i=1(ai-x)^2

赛时本题做题想法:看上去就不会,求距离用的宽搜,输出还是不对,直接输出rand取随机数。

题解:说明需要维护各距离的平方和,以及各距离的和。假设1为整棵树的根,求出将这棵树分成两半。向上求假设已有儿子答案,就将距离的平方和以及各距离的和到父亲的求出。然后考虑在其他点上。向下传递将距离的递加。

AC 代码:

#include <bits/stdc++.h>
using namespace std;
#define ll unsigned long long
const int maxn = 1e5 + 10;
ll sum2[maxn],sum1[maxn],sz[maxn],n,res;
vector<int>G[maxn];
void dfs1(int u,int f){
   for(auto v: G[u]){
     if(v==f)continue;
     dfs1(v,u);
     sz[u]+=sz[v];
     sum1[u]+=sum1[v];
     sum2[u]+=sum2[v];
   }
   sum2[u]+=sz[u]+2*sum1[u];
   sum1[u]+=sz[u];
   sz[u]+=1;
   return;
}
void dfs2(int u,int f,ll s1,ll s2){
   res=min(res,n*(s2+sum2[u])-(sum1[u]+s1)*(sum1[u]+s1));
   for(auto v: G[u]){
     if(v==f)continue;
     ll ret1=sum1[u]-(sum1[v]+sz[v])+s1;
     ll ret2=sum2[u]-(sum2[v]+2*sum1[v]+sz[v])+s2;
     ll szu=n-sz[v];
     dfs2(v,u,ret1+szu,ret2+2*ret1+szu);
   }
   return;
}
int main(){
   int t;
   cin>>t;
   while(t--){
     cin>>n;
     for(int i=1;i<=n;i++){
        G[i].clear();
        sum1[i]=sum2[i]=sz[i]=0;
     }
     for(inti=1;i<=n-1;++i){
        int u,v;
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
     }
     res=LONG_LONG_MAX;
     dfs1(1,0);
     dfs2(1,0,0,0);
     cout<<res<<endl;
   }
   return 0;
}

4. 赛后总结:

本次比赛中,思路还是不够打开,有些原来学习过的小但是实用的点没有想起来,数学知识也有待增加,之后要多多复习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值