CSP-J模拟赛三李奕岑补题报告
日期:2023-10-02 周一
学号:S10228
文章目录
一:
总分数:190
T1 [数字对应(digit)]:60分
T2 [技能学习(skill)]:100分
T3 [等于(equal)]:30分
T4 [最小方差(variance)]:0分
二:比赛过程
最开始第一道题不太会,然后做第二道题。
通过反复调试,得了100分。
接着回去做第一道题,用桶的方法拿了60分。
第三道题不会,拿了个枚举暴力和特殊分共30分。
最后一道题也不会,想拿特殊分没拿到。
三:题目分析
[数字对应(digit)]
1、题目大意
给定一个长度为n的序列A,让序列A的每个数字对应一个正整数,组成序列B。
序列B里的数字不能从序列A里出现过,并且序列A中第i个数与第j个数对应。相同的数对应的也是相同的数。
输出字典序最小的序列B。
2、比赛中的思考
最开始想了各种方法,最终决定用桶拿部分分。
先统计每个数的数目,然后寻找序列A中没出现过的数,挨个输出。
3、解题思路
我的思路是对的。就是桶数组要该成map数组。
map是STL里的。这里我们把它看作一个桶使用,只是空间没有下标。
4、AC代码
#include<iostream>
#include<map>
using namespace std;
int a[100005];
map<int,int>b,bb;
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
b[a[i]]++;
}
int t=1;
for(int i=1;i<=n;i++){
if(bb[a[i]]){
cout<<bb[a[i]]<<" ";
}
else{
while(b[t]!=0){
t++;
}
b[t]=1;
bb[a[i]]=t;
cout<<bb[a[i]]<<" ";
}
}
return 0;
}
[技能学习(skill)]
1、题目大意
n个同学学习新技能。
有m份资料,可以随意分给每位同学。
同学的资料不足k分将无法学习。
同学拿到p份资料,每分钟增长p个技能点,
但上限为q。
共有t分钟,求同学们的技能点数之和最大值。
2、比赛中的思考
最开始我一看到数据范围快爆int就知道要找规律。
首先,要先尽量给每位同学分k份,如果有剩余,就尽量平均分给每位同学。
然后计算每位同学总增长量。
最后判断是否超限并输出。
3、解题思路
思路就是这样的,为了保险要开long long。
4、AC代码
#include<iostream>
#include<cstdio>
using namespace std;
long long n,m,k,q,t,k1,k2;
int main(){
freopen("skill.in","r",stdin);
freopen("skill.out","w",stdout);
cin>>n>>m>>k>>q>>t;
if(k*n<m){
m-=k*n;
k1=k+m/n;
k2=m%n;
}
else if(k*n==m){
k1=k;
}
else{
n=m/k;
m=m%k;
k1=k+m/n;
k2=m%n;
}
if(k1*t>=q){
cout<<n*q;
}
else if(k1*t<q&&(k1+1)*t>=q){
cout<<k2*q+(n-k2)*k1*t;
}
else{
cout<<n*k1*t+k2*t;
}
fclose(stdin);
fclose(stdout);
return 0;
}
[等于(equal)]
1、题目大意
给定一个长度为n的序列,序列里每个元素属于-2,-1,1,2中的一个。
请问多少个子数组满足最大值的绝对值等于最小值的绝对值。
2、比赛中的思考
当时没有思绪,就纯暴力写完了。
3、解题思路
这道题可以分三种情况:
1:子数组全是相同的数;
2:最大值是1,最小值是-1;
3:最大值是2,最小值是-2;
统计三种情况子序列的总个数并输出。
4、AC代码
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const int maxn=5e5+10;
const int inf=0x3f3f3f3f;
int n;
ll ans,num[maxn];
int nxt[maxn][5];
ll startpos,endpos;
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{//不同就计算之前cnt个相同数字能搞出几个子集合
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][1+2];//遍历到i位置后面最近出现的1在哪里
int maxpos2=nxt[i][2+2];//遍历到i位置后面最近出现的2在哪里
int minpos1=nxt[i][-1+2];//遍历到i位置后面最近出现的-1在哪里
int minpos2=nxt[i][-2+2];//遍历到i位置后面最近出现的-2在哪里
startpos=max(maxpos2,minpos2);
//-2和2序列之间可以随便夹杂别的东西,当然 右端也一样随便加
endpos=n+1;//从右到左算就行了
if(startpos!=inf&&startpos<endpos){
//能计算的前提是找到了-2和2的一个闭区间才行
ans+=endpos-startpos;
//这个闭区间算一个,同时与右侧形成endpos-startpos-1个加闭区间自己就行了
}
startpos=max(maxpos1,minpos1);//找一个-1与1的闭区间的右端点
endpos=min(min(maxpos2,minpos2),n+1);
//保证区间内不能有2或者-2,并且向右拓展区间的时候也不能有
if(startpos!=inf&&startpos<endpos){//有-1与1的闭区间,并且没有2与-2
ans+=endpos-startpos;//同上 当场计算
}
}
cout<<ans;
return 0;
}
[最小方差(variance)]
1、题目大意
给定一个无根树T。
T含有N个点,N-1条边,边权全部为1。
在T中找一个树根root。
树根确定后计算出每个点到root的距离,得到一个长度为n的序列a。
请让序列a的方差最小。
输出的方差乘以n方。
2、比赛中的思考
不会,写了个特殊部分的情况有一个地方失误了。
3、解题思路
用查找(递归)的方式第一次算了每个节点的贡献,第二次算了根在哪里最优,最后输出。
4、AC代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<climits>
using namespace std;
#define ll long long
const int N=1e5+5;
int t,head[N],vis[N],v[N],nex[N],cnt;
ll n,Size[N],sum1[N],sum2[N],ans;
//sum1[x]以x为根的子树所有点到x的距离和
//sum2[x]以x为根的子树所有点到x的距离的平方和
//size[x]以x为根的子树中结点的数量
void add(int x,int y){
v[++cnt]=y;
nex[cnt]=head[x];
head[x]=cnt;
}
void dfs(int x){
vis[x]=1;//标记
//如果已知了孩子的sum1,那么转移到其父亲的sum1中时,所有孩子的孩子的距离+1
for(int i=head[x];~i;i=nex[i]){//遍历x的孩子
int y=v[i];//y是x的孩子
if(vis[y]) continue;//如果y已经看过 跳过
dfs(y);//继续深搜y
Size[x]+=Size[y];//x为根的树的结点个数+其孩子v为根的结点个数
sum1[x]+=sum1[y];//距离和转移
sum2[x]+=sum2[y];//距离平方和转移
}
sum2[x]+=Size[x]+2*sum1[x];
//32行和33行顺序不能颠倒:因为32行中用的sum1是没有额外距离的sum1
sum1[x]+=Size[x];//距离和中,所有子树转移时,每个结点到根x的距离应该+1
Size[x]+=1;//以x为根的树节点个数+1,算上了x本身
}
void dfss(int x,ll s1,ll s2){
vis[x]=1;//标记
//s1是x这个子树其父亲那部分距离和的贡献
//s2是x这个子树其父亲那部分距离平方和的贡献
//以x为根的公式 序列方差*n方公式:n*距离平方和-距离和的平方
ans=min(ans,n*(s2+sum2[x])-(sum1[x]+s1)*(sum1[x]+s1));
for(int i=head[x];~i;i=nex[i]){
int y=v[i];
if(vis[y]) continue;
//其父节点的那一半的贡献,总量减去v子树的贡献+父亲的父亲的贡献
ll ans1=sum1[x]-(sum1[y]+Size[y])+s1;//和版本
ll ans2=sum2[x]-(sum2[y]+2*sum1[y]+Size[y])+s2;//平方和版本
ll sz=n-Size[y];//其父节点另一边的子树的节点个数
//y作为孩子,x作为父亲
//其父亲给其和贡献 s1+每个点距离+1
//其父亲给其平方和贡献 父亲那边子树的平方和+2倍的和+节点个数
dfss(y,ans1+sz,ans2+2*ans1+sz);
}
}
int main(){
cin>>t;
while(t--){
//初始化
memset(head,-1,sizeof head);
memset(vis,0,sizeof vis);
ans=LLONG_MAX;
cnt=0;//重新存图
cin>>n;
for(int i=1;i<=n;i++){
sum1[i]=sum2[i]=Size[i]=0;
}
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);//无向图
}
dfs(1);//假设1是根
memset(vis,0,sizeof vis);
//s1是其父亲对应的子树(另一半)给他的距离和的贡献值
//s2是其父亲对应的子树(另一半)给他的距离平方和的贡献值
dfss(1,0,0);
cout<<ans<<endl;
}
return 0;
}