题目分析
第一题-数字对应
题目描述
给定一个长度为 n 的序列 A,给序列 A 中每个数字找出一个对应的正整数,构成一个长度为 n 的序列 B。
序列 B 中数字不能在序列 A 中出现过,并且序列 A 中第 i 个正整数与 序列 B 中的第 i 个正整数对应。对应关系可以随意指定,但是必须唯一。例如序列 A 中的数字 3 对应序列 B 中的数字 4,那么序列 A 中所有的 3 都必须对应序列 B 中的 4,并且序列 A 中其余数字不能再对应序列B中的 4。
请你输出字典序最小的序列 B。
思考过程
一开始我进行了暴力枚举,尽量将前面的数字对应成小的数字。即假设当前数字没有对应的数字,那么就从小到大寻找第一 个未出现过的数字,对应到当前数字上,但是这种方法不仅效率低,而且还容易时间超限。
所以我们首先引入bool类型的vis数组来标记数字是否被使用(表示每一个数字是否出现过),因为随后,使用 map 进行映射,最后与原集合一一对应,输出我们已经映射好的值就行.
期望得分
期望得分60,实际得分40,出现偏差的原因为:
对于做题的方法掌握得不够透彻,尤其是没有想到map,并且暴力方法虽然好打,但代价就是时间复杂度过高(时间复杂度是O(n²),甚至更高),所以没有拿到预计得分和全分
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20;
map<ll,ll>vis,mp;
ll n,cnt=1,a[2*N];
int main()
{
//freopen("digit.in","r",stdin);
//freopen("digit.out","w",stdout);
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
scanf("%lld",a+i);
vis[a[i]]=true;
}
for(ll i=1;i<=n;i++)
{
if(mp[a[i]]==0)
{
while(vis[cnt]) cnt++;
vis[cnt]=1,mp[a[i]]=cnt;
}
printf("%lld ",mp[a[i]]);
}
return 0;
}
第二题-技能学习
题目描述
有 n 个同学正在学习新技能,技能的学习与小可老师给的学习资料数量有关系。
已知小可老师有 m 份学习资料,并且小可老师可以将学习资料随意分给每位同学。但是某位同学如果学习资料数量不足 k 份,将掌握不了新技能。
同学学习新技能与学习资料的数量成正比,比如一位同学拿到了 p 份学习资料,那么每单位时间(分钟)会增长 p 点技能点,但是同学技能点最多到 Q,就表示已经完全学会了此技能,并且后续时间内也不会再继续学习,技能点也不再增长。
现在总共有 t 分钟,小可老师希望同学们的技能点数之和最多,请问技能点最多是多少?
注意:小可老师会在第0分钟时,将所有学习资料发放完毕(学习资料必须按照整份发放),之后不能再调整。
思考过程
首先,我考虑的是可以枚举挨个发放学习资料,暴力求出答案,结果因为时间超限卡掉了一半的分。
其实我们可以贪心考虑一下,应该尽可能多得让同学提升技能点,如果将学习资料全部发放给少数几位同学,那么也很容易到达上限。而学习资料发放的越分散,就越不容易到达上限。所以我们将学习资料尽可能平均分给能提升技能点的同学.特判时,尤其要注意m<k这种情况,否则会影响后面答案的求解.
期望得分
期望得分70,实际得分40,出现偏差的原因为:
1.没有考虑到m<k的情况,可能会导致丢掉一部分分.
2.计算ans的方法不仅繁琐,而且占用了大量的时间和空间,导致一部分数据时间超限.
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20;
ll n,m,k,q,p,t,x,pos,cnt,ans,num[N];
int main()
{
//freopen("skill.in","r",stdin);
//freopen("skill.out","w",stdout);
scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&q,&t);
if(m<k)
{
printf("0");
return 0;
}
else if(n*k>m) n=m/k;
x=m/n;
ans=min(x*t,q)*(n-m%n);
ans+=min((x+1)*t,q)*(m%n);
printf("%lld",ans);
return 0;
}
第三题-等于
题目描述
给定一个长度为 n 的序列,并且序列中每个元素属于 -2,-1,1,2
中的一个。
请问多少个 子数组 满足最大值的绝对值等于最小值的绝对值。
思考过程
我一开始的想法就是打暴力去尽可能多的拿分,虽然最后的成果可能有一些不尽人意.
区间最大值和最小值的绝对值相等时,存在两种情形:
第一种情形:区间内所有元素相同。这种情况较为简单,通过遍历数组并统计连续相同元素即可处理。
第二种情形:区间最大值与最小值为相反数。具体表现为两种情况:
- 最大值为1,最小值为-1
- 最大值为2,最小值为-2
针对第二种情形的求解方法如下: 当固定左端点时,寻找合法右端点的规则:
-
对于最大值为1、最小值为-1的情况:
- 右端点需满足:区间包含1和-1,但不含2和-2
- 可通过维护左端点右侧1、-1、2、-2首次出现的位置来确定
- 右端点起始位置取1和-1首次出现位置的最大值
- 结束位置取2和-2首次出现位置的最小值
-
对于最大值为2、最小值为-2的情况:类似上述方法处理,需保证区间包含2和-2.该算法的时间复杂度为O(n)。
期望得分
期望得分40,实际得分10,出现偏差的原因为:
1.思考的方式有所偏差,只有全局思维,应该可以先尝试拿到部分分,在考虑所有数据.
2.对于题目的理解有出入(很大出入,比如没有看见最小值这件事),导致丢掉了许多本来拿到的分.
完整代码
注:第一组是40分的暴力枚举.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20;
ll n,ans,len=1,a[N];
int main()
{
//freopen("equal.in","r",stdin);
//freopen("equal.out","w",stdout);
scanf("%d",&n);
for(ll i=1;i<=n;i++) scanf("%d",a+i);
a[n+1]=3;
for(ll i=1;i<=n;i++)
{
if(abs(a[i])!=abs(a[i+1]))
ans+=(len+len*(len-1)/2),len=1;
else len++;
}
printf("%lld",ans);
return 0;
}
注:下面附80分代码(我也不知道哪错了)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20,INF=0x3f3f3f3f;
ll n,ans,pos,r,len=1,a[N],p1[N],p2[N];
int main()
{
//freopen("equal.in","r",stdin);
//freopen("equal.out","w",stdout);
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
scanf("%lld",a+i);
if(a[i]==-1) a[i]=1;
}
a[n+1]=3;
for(ll i=1;i<=n;i++)
{
if(a[i]!=a[i+1]) ans+=(len+len*(len-1)/2),len=1;
else len++;
}
pos=INF;
for(ll i=n;i>=1;i--)
{
if(a[i]==2) pos=i;
p1[i]=pos;
}
pos=INF;
for(ll i=n;i>=1;i--)
{
if(a[i]==-2) pos=i;
p2[i]=pos;
}
for(ll l=1;l<=n;l++)
{
r=max(p1[l],p2[l]);
if(r==INF) break;
ans+=(n-r+1);
}
printf("%lld",ans);
return 0;
}
注:AC代码摘抄于题目解析-STD3:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;const int inf=0x3f3f3f3f;
ll n,ans,num[maxn];
int nxt[maxn][5];
int pos1,pos2;
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];
ll 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];
pos1=max(maxpos2,minpos2);
pos2=n+1;
if (pos1!=inf&&pos1<pos2) ans+=pos2-pos1;
pos1=max(maxpos1,minpos1),pos2=min(min(maxpos2,minpos2),(int)n+1);
if (pos1!=inf&&pos1<pos2) ans+=pos2-pos1;
}
cout<<ans<<'\n';
return 0;
}
第四题-最小方差
题目描述
给定一颗无根树
T。
已知 T 包含 n 个点,n−1 条边,且边权全部为 1,请在 T 中寻找一个树根 root,当树根确定后计算出树上每个点到 root 的距离,得到一个长度为 n 的序列 a。请让序列 a 的方差最小。
为了方便输出,输出的方差值乘以 n2。
提示:设序列 a 的平均值为 x,那么乘以 n2 后的方差为 n×∑i=1n(ai−x)2
思考过程
注:另一种更方便的方差公式:n × ∑ ( a² ) - ( ∑ a )².
注:思路摘抄于题目解析-STD4:
30%数据下:可以直接暴力枚举每个点作为根的情况,然后dfs出每个点到根的距离,计算方差。另外的20%数据下:为一条链表的情况,那么根设置在中间的位置是优的。
100%数据下:考虑换根DP
首先考虑方差的计算式,假设选择点 为根,各点距离为 ,则此时有
说明我们需要维护各距离的平方和,以及各距离的和。假设 1 为整棵树的根,考虑怎么向上计算出结果。
令 sum1[u] 表示以 为根的子树上各点到 的距离和, sum2[u] 表示以 为根的子树上各点到 的距离平方和。假设已有儿子 的答案,考虑如何转移到父亲 上:显然,儿子上所有店到父亲的距离会在 原来的基础上加一。因此发现需要维护子树上的点数量,记为 sz[u]
此时可以写出转移式:
这样我们就可以算出以 为根时整棵树的方差,这也是第一次 dfs 做的事。接下来考虑如果换成其它点作为根,答案会有怎么样的变化。
某一个节点为根时,方差的答案有两个来源:一个是该结点所在的子树的贡献(这里的子树指的是假设为根时的子树),还有一个来源是当前点上方的祖先结点对的贡献。(祖先也是指 为根时这个点的祖先)
前者我们已经在第一次 dfs 中求得。我们来考虑第二个问题如何计算。首先,将子树的影响从父节点中去掉:
这个点的子树对父亲结点的贡献为:1. 对 sz 的贡献:
2. 对 sum1 的贡献:
3. 对 sum2 的贡献:
不妨将它们记为 szu,ret1,ret2
从根向下传时,
然后从根出发进行 dfs,向下的过程中记录维护 , 的变化,来获得之后的点作为根时整棵树的方差。
期望得分
期望得分0,实际得分0,主要是对于这一块的知识点掌握不熟练
完整代码
注:AC代码摘抄于题目解析-STD4:
#include <bits/stdc++.h>
#define ll unsigned long long
using namespace std;
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,ret2=sum2[u]-(sum2[v]+2*sum1[v]+sz[v])+s2,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;
}
}
比赛分析
做题顺序与时间分配
这次对于时间的规划和做题熟练度于昨天有一定的进步,前两题分配到的时间算是比较合理,对于后两道题所分配到的时间也相对来说比较合理.可能对于第四题来说,45分钟的时间比较短,但是也是足够用的(因为第四题根本不会),所以对于时间分配这一块,考试中的把握已经趋于成熟.
考试心态
大家应该培养在考试中更强的抗压能力用以在考试中发挥正常.——马老师
这次考试的发挥和得分相较于昨天有了显著的进步(虽然还是丢了很多不该丢的分),保持住这种做题状态,争取在考试中能有更好的发挥!