CSP-J复赛模拟赛3赛后补题报告

日期:2023年10月3日星期二 

学号:S07731 
姓名:魏仲毅 

1.比赛概况: 

     比赛总分共4题,满分400。
     赛时拿到60分,其中第一题0分,第二题60分,第三题0分,第四题0分。 

2.比赛过程: 

一开始觉得第一题比较简单,就死磕第一题,导致后面只有1小时5分钟来做2,3,4题(笑死,还是0分)。

第二题感觉用几个判断就解决了(也真是这样)。

第三题打了一个暴力,解决大部分的样例(没有优化成为了超时的关键)。

第四题完全没有思路,就输出了几个样例就完事了。

3.题解报告: 
  (1) 第一题:数字对应(digit)

        重点:STL--map。
        情况:赛中0分,已补题。
        题意:序列 B 中数字不能在序列 A 中出现过,并且序列 A 中第 i 个正整数与 序列 B 中的第 i 个正整数对应。对应关系可以随意指定,但是必须唯一。输出字典序最小的序列 B。
        赛时本题做题想法:用桶标记,接着从小到大遍历一遍,把没有标记过的存一下,接着按要求输出。
        题解:标记数字是否被使用,可以使用一个map来表示每一个数字是否出现过,找到第一个没出现的数字后,接着用另一个map来建立一个映射关系,最后用映射代替原数组输出即可。
        来看一看AC 代码:

#include<bits/stdc++.h>
using namespace std;
map <int,int> b,mp;
const int N=1e5+5;
int a[N];
int main(){
	//freopen("digit.in","r",stdin);
	//freopen("digit.out","w",stdout);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[a[i]]++;
	}
	int idx=1;
	for(int i=1;i<=n;i++){
		if(mp[a[i]]){
			cout<<mp[a[i]]<<" ";
		}
		else{
			while(b[idx]){
				idx++;
			}
			mp[a[i]]=idx;
			b[idx]=1;
			cout<<idx<<" ";
		}
	}
	//fclose(stdin);
	//fclose(stdout);
    return 0;
}
(2) 第二题:技能学习(skill)

         重点:分类讨论思想。
         情况:赛中60分,已补题。 
         题意:有n个同学正在学习新技能,已知小可有m份学习资料,并且小可老师可以将学习资料随意分给每位同学。但是如果某位同学学习资料数量不足k份就无法学习。一位同学拿到了p份学习资料,那么每分钟会增长p点技能点,但是同学技能点最多到Q,就表示已经完全学会了此技能,技能点不再增长。现在总共有t分钟,求技能点最多是多少?

         赛时本题做题想法:通过while循环,求出可以给谁学习,再求出三种情况的总技能点。
         题解:首先,如果学习资料不足k份,没有人能学习 ,输出0。其次m份资料不够所有人学习 ,就先算出实际能满足几个人,和减去可以学习的人的消耗,剩下多少份资料 。根据剩余资料的数目划分两类人:没有多分的和多分的。如果不能平分剩下的资料 ,则看看平分的话多出多少份资料,也就是有多少人多了 。最后求出没有多分的人的总技能点和多分的人的总技能点,将相加得到的总技能点输出即可。
         AC 代码如下:

#include<bits/stdc++.h>
using namespace std;
long long n,m,k,q,t,f1,n1,f2,n2,ans;
int main(){
	//freopen("skill.in","r",stdin);
	//freopen("skill.out","w",stdout);
	cin>>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!=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;
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}
(3) 第三题:等于(equal)

        情况:赛中0分,已补题。 
        题意:给定一个长度为n的序列,并且序列中每个元素属于 -2,-1,1,2中的一个。请问多少个子数组满足最大值的绝对值等于最小值的绝对值。
        赛时本题做题想法:n个数最少有n个,再如果有相同的数或互为相反数的值,再累加起来
        题解:没有记录前先初始化一下nxt,接着输入每个位置的数字 。下一步,不改变数组顺序,看连续相同数字有多少;如果当前数字和上一个数字一样就继续计数 。不一样的时候重新累计,同一个数字也是符合条件的。之后从后往前计算最后一个数字。注意:所有数字第一次出现的下标先继承上一行。继续将当前数字num[i]对应的位置更新成当前位置i,找到1,2,-1,-2在i之后第一次出现的下标 。第一步找到2和-2中下标靠后的数字后,确保i~n中2和-2都是有的,因为前面已经算过只有单个数字的了 。第二步找到1和-1中下标靠后的数字,但要考虑不能在区间中出现2,所以最大到目前下标最小的2或者-2,没有则也是最后。i~n中有1和-1,且中间没有夹杂2和-2时记一下数。最后输出总方案数即可。
        AC 代码↓↓↓: 

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
const int INF=0x3f3f3f3f;
long long n,ans,num[N];
long long nxt[N][5];
long long startpos, endpos;
int main(){
	//freopen("equal.in","r",stdin);
	//freopen("equal.out","w",stdout);
	cin>>n;
	memset(nxt,0x3f,sizeof(nxt));
	for(int i=1;i<=n;i++){
		cin>>num[i];
	}
	long long 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][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;
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

  (4)  第四题:最小方差(variance)

        重点:深搜(dfs)。
        情况:赛中0分,已补题。 
        题意:已知T包含n个点,n−1 条边,且边权全部为1,请在T中寻找一个树根root,当树根确定后计算出树上每个点到root的距离,得到一个长度为n的序列a。请让序列a的方差最小。为了方便输出,输出的方差值乘以n^2​​。
        赛时本题做题想法:没有思路。
        题解:应使用dp来做,也可以用dfs。一开始把u和v之间有一条边记录下来 。之后res初始化最大值 。将1作为初始根,其父亲是0,进行第一轮深搜 。

在第一次深搜中,u是当前搜的点,子树的根,f是其父节点 。如果已知了孩子的sum1,那么转移到其父亲的sum1中时,所有孩子的孩子的距离+1 。上来先遍历u的孩子,因为是双向存储,如果当前遍历到其父亲则跳过。否则继续以v为起点深搜,u是其父亲 。接着用u为根的树的结点个数+其孩子v为根的结点个数 转移一下距离和和距离平方和。(公式转换:u是v的父亲,如果v的距离平方和是(x1)^2+(x2)^2+(x3)^2,这些点到u的距离统一+1,则平方和变化(x1+1)^2+(x2+1)^2+(x3+1)^2.拆后得知:x1^2+2*x1+1  +x2^2+2*x2+1  +x3^2+2*x3+1。即 x1^2+x2^2+x3^2 +2(x1+x2+x3) +3。即 sum2[v]+2*sum1[v]+子节点个数 )注意:25行和26行顺序不能颠倒:因为25行中用的sum1是没有额外距离的sum1 。然后在距离和中,所有子树转移时,每个结点到根u的距离应该+1 。因为算上了u本身,所以再以u为根的树节点个数+1 。第一轮深搜结束。

第二轮深搜中,u作为根,f是其父亲,s1是u这个子树对其父亲距离和的贡献,s2是u这个子树对其父亲的距离平方和的贡献(以u为根的公式 序列方差*n方公式:n*距离平方和-距离和的平方 )。再次遍历u的邻接点 ,v恰好是父亲时,越过 。当v作为孩子,u作为父亲时,其父亲给其和贡献s1+每个点距离+1;其父亲给其平方和贡献 父亲那边子树的平方和+2倍的和+节点个数 。继续向下深搜,结束。
        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(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. 赛后总结: 

本次比赛出现了时间不够用,思路不够开拓的老毛病。以后需要合理规划时间,前两道题可以用时1.75小时,不要被一道题卡住太久。还是那句话,后两道题如果没有思路,不用非要全对,30,40也可以。再就是,如果一道题想不出来,就可以联想一下之前学习过的知识,有助于解题。

加油!继续加油!胜利就在前方!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值