复赛模拟 Day3 赛后补题报告

日期: 2023 年 10 月 2 日星期一

学号:S07892

1. 比赛概况:

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

2.比赛过程:

        第一题看到数据过大时想到用map去做标记数组,中途比较波折,还曾想过用集合去求出有几个非重数,但经过一个半多小时的思考,更改了想法,简洁了自己的代码,第二题就只需要用几个特判(或几个判断表达式),O(1)时间复杂就过了,第三题没能有充分时间阅读题目,直接进行了特殊样例的判断编写,得到了特殊样例的分,第四题缺乏时间,未进行代码编写,赛后准备拷贝代码时电脑还突然崩了,中午又苦苦打了一遍。

3. 题解报告:

(1)第一题:数字对应(digit)

情况:赛中100分

题意:给一个长度为n的序列A,A中每个数找一个对应的正整数对应到B序列,每个数字对应唯一,求出字典序最小的序列B。

赛时本题做题想法:看到数据过大想用map去做标记数组,还想用集合去求出有几个非重数

后来发现求出非重元素个数过于繁琐,将其优化成循环中输出。

题解:运用两个map,一个进行标记,一个存储对应的数字,从小到大循环,判断是否有对应数字,若没有,则从小到大判断是否标记进行查找最小对应。

AC 代码:

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<map>
using namespace std;
map<int,int> v,m;
int n,a[100005],b[100005],maxx,minn,mi=1,ma,f;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+1+n);
	int x=b[1];
	v[x]=1,minn=x;
	for(int i=2;i<=n;i++){
		if(b[i]!=x){
			x=b[i];
			v[x]=1;
			maxx=x;
		}
	}
	ma=maxx+1;
	for(int i=1;i<=n;i++){
		if(m[a[i]]!=0){
			printf("%d ",m[a[i]]);
		}
		else{
			if(mi<minn){
				printf("%d ",mi);
				m[a[i]]=mi;
				mi++;
			}
			else{
				if(f==0){
					x=0;
					for(int j=minn+1;j<maxx;j++){
						if(v[j]!=1){
							printf("%d ",j);
							m[a[i]]=j;
							minn=j;
							mi=j;
							x=1;
							break;
						}
					}
					if(x==0){
						f=1;
					}
				}
				if(f==1){
					printf("%d ",ma);
					m[a[i]]=ma;
					ma++;
				}
			}
		}
	}
	return 0;
}

(2)第二题:技能学习(skill)

情况:赛中100分

题意:有n个人,有m份资料,学习至少需要k份资料,每人拥有几份资料每分钟就能产生几个技能点,有t分钟,每人最多得q点,求最大能获得的几点技能点

赛时本题做题想法:因为每人可能会有限制,所以越平均分发资料总收入越高,就尽可能平均分,多出的也一人就一份,简单推出算式加特判就OK了。

题解:数学题,推出计算需要量,加限制特判,相加得出

AC 代码:

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<map>
using namespace std;
long long n,m,k,q,t;
long long r,f,e,gp,gt,sum;
int main(){
	scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&q,&t);
	r=m/k;
	if(r>n){
		r=n;
	}
	f=m/r;
	e=m-f*r;
	if(e==0){
		if(q<f*t){
			gp=q;
		}
		else{
			gp=f*t;
		}
	}
	else{
		if(q<f*t){
			gp=q;
			gt=q;
		}
		else{
			gp=f*t;
			if(q<(f+1)*t){
				gt=q;
			}
			else{
				gt=(f+1)*t;
			}
		}
	}
	sum=gp*(r-e)+gt*e;
	printf("%lld",sum);
	return 0;
}

(3)第三题:等于(equal)

情况:赛中20分,已补题

题意:有一个长为n的序列,序列元素一定为-2,-1,1,2中的一个,求出有多少个子序列的最大值的绝对值等于最小值的绝对值

赛时本题做题想法:时间过紧,只看了特殊样例,判断了是否序列所有数相等

题解:固定左端点,情况一,区间内所有数相同,情况二,分成是否含绝对值为2的数的两种情况

AC 代码:

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<map>
#define ll long long
using namespace std;
ll n,ans,num[500005];
int nxt[500005][5],startpos,endpos;
int main(){
	scanf("%lld",&n);
	memset(nxt,0x3f,sizeof(nxt));
	for(int i=1;i<=n;++i){
		scanf("%lld",&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;//记录a[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);//右端点为2或-2中靠右的一个 
		endpos=n+1;
		if(startpos!=0x3f3f3f3f&&startpos<endpos){
			ans+=endpos-startpos;
		}
		startpos=max(maxpos1,minpos1);//右端点为1或-1中靠右的一个 
		endpos=min(min(maxpos2,minpos2),(int)n+1);//不能取到2或-2,所以取最小值 
		if(startpos!=0x3f3f3f3f&&startpos<endpos){
			ans+=endpos-startpos;
		}
	}//情况二:正负数都存在 
	printf("%lld\n",ans);
	return 0;
}

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

情况:赛中0分,已补题

题意:有一颗无根树,已知包含n个点,n-1条边,且边权全部为1,请在数中寻找一个树根 ,当树根确定后计算出树上每个点到根的距离,得到一个长度为n的序列a。请让序列a的方差最小。

赛时本题做题想法:无

题解:二次扫描法,先进性式子推导,将平均值x去掉,避免小数double丢失精度,再进行两次搜索

\sum_{i=1}^{n}

1.preview:

n\sum (a_{i}-x)^{2}\\ \Rightarrow n\sum(a{_{i}}^{2}+x^{2}-2a_{i}x)\\ \Rightarrow n\sum a{_{i}}^{2}+n\sum x^{2}-n\sum 2a_{i}x\\ \Rightarrow n\sum a{_{i}}^{2}+n^{2}x^{2}-2nx\sum a_{i}\\ \Rightarrow n\sum a{_{}}^{}+n^{2}x^{2}\\ \Rightarrow n\sum a{_{i}}^{2}-(\sum a_{i})^{2}

2.dfs1:

sum1[u]:以u为根的子树中,每个点到u的距离的和

sum[2]:以u为根的子树中,每个点到u的距离的平方的和

\begin{aligned} &sum1[v]=\sum a_i\\ &\Rightarrow sum1=\sum (a_i+1)\\ &=\sum a_i+\sum1\\ &=sum1[v]+sz[v] \end{aligned}\\\\\\ \begin{aligned} &sum2[v]=\sum a{_{i}}^{2}\\ &\Rightarrow \sum(a_i+1)^2\\ &=\sum(a{_{i}}^{2}+1+2a_i)\\ &=\sum a{_{i}}^{2}+\sum1+2\sum a_i\\ &=sum2[v]+sz[v]+2sum1[v] \end{aligned}

2.dfs2:

//与dfs1是相似的,二次扫描
void dfs2(int u,int f,unsigned long long s1,unsigned long long 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;
		}
		unsigned long long 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;
}

AC 代码:

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<map>
using namespace std;
unsigned long long sum2[100010],sum1[100010],sz[100010],n,res;
vector<int> G[100010];
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,unsigned long long s1,unsigned long long 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;
		}
		unsigned long long 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;
	scanf("%d",&t);
	while(t--){
		scanf("%lld",&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;
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		res=1e18;
		dfs1(1,0);
		dfs2(1,0,0,0);
		printf("%lld\n",res);
	}
	return 0;
}

4. 赛后总结:

        本次比赛出现了时间分配不均的问题,应多加进行训练,加强自己的代码编写速度,防止有题目无法仔细思考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值