CSP-J模拟赛三李奕岑补题报告

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值