日期:2023年10月4日星期三
S10698
1.比赛概况
比赛共有4题,满分400分,赛时拿到10分,其中第一题爆零,第二题爆零,第三题爆零,第四题10分。
2.比赛过程
首先对题目进行了一遍浏览,感觉第四题很简单,然后就做了第四题。因为对第四题代码内容的运用不够熟练,以及最后才发现题目求的是最小的方差,所以在第四题上用了接近两个小时的时间,导致做后面题目的时间比较紧张。然后我又做了第三题,看到数据提示写着有10%的数据绝对值都相同,我就根据这10%的数据推出了一个自以为非常正确的数学公式来做这道题,最后也没对,做第二题的时候只剩下不到五分钟了,刚打完头文件和freopen,比赛就结束了。
3.题解报告
(1)第一题:数字对应(digit)
情况:赛中爆零,已补题。
题意:给了你一个长度为n的序列A,让你替换序列A的每个元素,构成一个序列B,输出字典序最小的序列。
【注意】:①序列B中出现的数字不可以与序列A中出现的数字重合。
②序列B中的数字只能对应一个序列A的数字。
③相同的数字与相同的数字对应
赛时本题做题想法:比赛时时间不够了,只根据几个样例和大数据样例进行了偏分,最后就爆零了。
题解:先循环n次输入序列A,存到数组里,同时用一个map映射的m数组进行当前数字是否有过的标记。再循环n次,如果当前t数组的值不为零,就直接输出,否则就在m[tot]值不为零的情况下,令tot自加,直到m[tot]没有用过,值为零,再将tot的值赋给t数组,并把m[tot]标记为一,随后输出tot。
AC代码:
#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
const int N=1e7+10,M=2e6+10;
int n,tot=1;
int a[N];
map <int,int>m,t;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
m[a[i]]=1;
}
for(int i=1;i<=n;i++)
{
if(t[a[i]]!=0)
{
printf("%d ",t[a[i]]);
}
else
{
while(m[tot]!=0)
{
tot++;
}
t[a[i]]=tot;
m[tot]=1;
printf("%d ",tot);
}
}
return 0;
}
(2)第二题:技能学习(skill)
情况:赛中爆零,已补题。
题意:有n个同学正在学习新技能,技能的学习与小可老师给的学习资料数量有关系。已知小可老师有m份学习资料,并且小可老师可以将学习资料随意分给每位同学。但是某位同学如果学习资料数量不足k份,将掌握不了新技能。同学学习新技能与学习资料的数量成正比,比如一位同学拿到了p份学习资料,那么每单位时间(分钟)会增长p点技能点,但是同学技能点最多到Q,就表示已经完全学会了此技能,并且后续时间内也不会再继续学习,技能点也不再增长。现在总共有t分钟,小可老师希望同学们的技能点数之和最多,请问技能点最多是多少?
赛时本题做题想法:做到这个题的时候只剩下不到五分钟了,只能打完头文件和freopen。
题解:贪心考虑一下,应该尽可能多得让同学提升技能点,如果将学习资料全部发放给少数几位同学,那么很容易到达上限。而学习资料发放的越分散,就越不容易到达上限。
AC代码:
#include <algorithm>
#include <cstdio>
using namespace std;
long long n, m, k, q, t, f1, n1, f2, n2, ans;
int main()
{
scanf("%lld %lld %lld %lld %lld",&n,&m,&k,&q,&t);
if(m<k)
{
printf("0");
return 0;
}
if(n*k>m)
{
n=m/k;
}
m-=n*k;
if(m%n)
{
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;
printf("%lld",ans);
return 0;
}
(3)第三题:等于(equal)
情况:赛中爆零,已补题。
题意:输入一个长度为n的序列,序列里的每个元素a都为-2、-1、1、2中的任意一个,让我们求它的子序列中(包括这个序列本身),有多少个子序列满足序列的最小值的绝对值等于子序列最大值的绝对值。
赛时本题做题想法:做完第四题后时间只剩下了一个小时,但是还有三个题没有做,就根据提示里给的有10%的数据的绝对值都相同,然后根据几个元素绝对值都相同的序列,推出了一个自以为正确的数学公式,就根据了这个数学公式来做题,最后也没得分。
题解:区间最大值和最小值的绝对值相等,有以下两种情况:
①区间中只有同一种数字。这个比较简单,通过简单的 for 循环向后计数得到。
②区间中最大值和最小值为相反数。
考虑固定左端点时,如何找合法的右端点:
① 对于最大值为最小值的情况:右端点需要保证:区间中有1和-1,区间中没有2和-2 。
这可以通过维护左端点向后的 1/-1/2/-2 的初始位置找到。右端点的起始位置为max (下一个1的位置,下一个-1的位置),结束位置为 min (下一个2的位置,下一个-2的位置)
②对于最大值为2最小值-2的情况:右端点需要保证:区间中有2和-2。维护方法类似上面。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
const int inf=0x3f3f3f3f;
long long n,ans,num[maxn];
int nxt[maxn][5];
int startpos,endpos;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
memset(nxt,0x3f,sizeof nxt);
for (int i=1;i<=n;++i)
{
cin>>num[i];
}
long long ret=1,lst = num[1];
for (int i=2;i<=n;++i)
{
if (num[i]==lst)
{
++ret;
}
else
{
ans+=ret*(ret+1)/2;
ret=1;
lst=num[i];
}
}
ans+=ret*(ret+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][1+2],maxpos2=nxt[i][2+2];
int minpos1=nxt[i][-1+2],minpos2=nxt[i][-2+2];
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<<'\n';
return 0;
}
(4)第四题:最小方差(variance)
情况:赛中10分,已补题。
题意:给你t棵无根树,循环t次输入每棵无根树的总点数n,然后输入n-1条边,但没有明确谁指向谁,所以我们并不能确定这两个点之间的关系。然后让我们找一个点,使得这个点为根时,所有节点深度序列的方差最小。最后让我们输出这棵无根树的最小方差。
赛时本题做题想法:认为这个题很简单,用链式前向星,暴力枚举假设枚举的每个点都是根,求出余下每个子节点的深度,也就是题目所说的序列内容,然后求出这个序列的平均值,最后算出当前节点为根时,方差为多少。最后求出这棵树节点深度的最好方差。但是我在做这个题时对上述过程的一些代码运用的不是很熟练,导致在这道题上花费了接近两个小时的时间。用了这个爆搜,最后也只拿到了10分,得不偿失。
题解:
总结一下就是根据方差的数学公式来求解答案。
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(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
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(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
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 (int i=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;
}
}
4.总结
①合理的去分配每个题的时间,不要在一颗树上吊死,得不偿失。
②在每个题目上都花一定时间好好想想,可能就是因为没有好好想,才认为题目很难,最后错失得分良机。
③跟数学有关的题目可以尝试的去找规律、公式,或者简化思维复杂度,减轻负担。