日期: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. 赛后总结:
本次比赛中,思路还是不够打开,有些原来学习过的小但是实用的点没有想起来,数学知识也有待增加,之后要多多复习。