2022CSP-CCF前三题 现值计算、训练计划、JPEG解码解题分析

3 篇文章 0 订阅


CCF考试系统传送门

2023.03第29次CCF-CSP计算机认证考试
CCF计算机软件能力认证考试系统


前言

最近真忙,五一可能也没时间了,之后的解题分析说不定就鸽了(悲)


一、现值计算

1.题目内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.题解分析

按题目给的公式来全部转换为第0年的价值,然后累加和。其中输入的顺序 j正好对应年份k。

解题关键公式:
在这里插入图片描述

3.完整代码

#include<bits/stdc++.h>
using namespace std;
const int MAX_N=60;
double n,i;
double money,ans=0.0;
int main()
{
	scanf("%lf%lf",&n,&i);
	for(int j=0;j<=n;j++)
	{
		scanf("%lf",&money);
		ans+=money*pow(1.0+i,(double)-j);
	}
	printf("%.3lf",ans);
	return 0;
}

评测结果
在这里插入图片描述


二、训练计划

1.题目内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.题解分析

写完之后,发现老师的留言:
在这里插入图片描述

再看了一眼关键路径,好吧,这应该接近关键路径算法的板子题了。

先留个位置,有时间补个关键路径做法:

关键路径

再说说我的做法。
数据结构:并查集
用了点并查集的思想。因为数据量小,所以可以用比较暴力的方法。

关键利用到并查集的寻根操作,我们可以将查找时经过的路径对应上一个权值(即训练时间)查找时将所有权值加起来,即可以得出最早时间和最晚时间。

最早时间比较简单,因为是并查集的建立方向

int caltime1(int x)
{
	int sum;
	sum=t[s[x]];//加下个结点的值
	return s[x]==x?sum:sum+caltime1(s[x]);
}
//在solve函数中调用:
if(model==1)
{
	day=caltime1(i)+1;
	if(day+t[i]-1>n)
	cmplt=0;//完成标记
}

求最晚时间时,因为逆着并查集建立方向,比较麻烦。对于求最晚时间的点,找其他哪个点的父亲/祖宗是它,如果是,求从那个点到待求点的路径权值和即可,然后维护一个最大的权值(即为最晚开始时间)


bool findroot(int x,int dest)//dest是否是x的父亲/祖宗
{
	if(x==dest)
		return 1;
	else if(s[x]==x)//为共同根0,则不为
		return 0;
	else return findroot(s[x],dest);	
}
int caltime2(int x,int dest)
{
	int sum;
	sum=t[x];//加上当前结点值
	return x==dest?sum:sum+caltime2(s[x],dest);
}
//在solve函数中调用:
else
{
	for(int j=m;j>=i;j--)
	{
		if(findroot(j,i))//j的父亲/祖宗是i
			day=max(day,caltime2(j,i));
	}
	day=n-day+1;
}

最复杂情况下复杂度为O( n 2 n^2 n2)

(之前写的,语无伦次的分析
这道题的核心,我们可以这样理解:先将每个点按关系连接,然后在每个关系中取时间线最长的作为主干,其他是分支。对于某个点,若它在主干上,最早开始时间与它的前缀和(不包括该点)有关系,最晚开始时间和它的后缀和(包括该点)有关系;若在分支上,则需要先找到分支直接相连的主干点,这条整条分支上的点的最早/晚开始时间都与这个主干点有关。

图解:
在这里插入图片描述

3.完整代码

#include<bits/stdc++.h>
using namespace std;
const int MAX_M=110;
int n,m,p;
bool cmplt=1;//默认可以完成
int s[MAX_M],t[MAX_M];
bool findroot(int x,int dest)
{
	if(x==dest)
		return 1;
	else if(s[x]==x)//为共同根0,则不为前缀
		return 0;
	else return findroot(s[x],dest);	
}
int caltime1(int x)
{
	int sum;
	sum=t[s[x]];//加下个结点的值
	return s[x]==x?sum:sum+caltime1(s[x]);
}
int caltime2(int x,int dest)
{
	int sum;
	sum=t[x];//加上当前结点值
	return x==dest?sum:sum+caltime2(s[x],dest);
}
void solve(int model)
{
	for(int i=1;i<=m;i++)
	{
		int day=0;
		if(model==1)
		{
			day=caltime1(i)+1;
			if(day+t[i]-1>n)
				cmplt=0;
		}
		else
		{
			for(int j=m;j>=i;j--)
			{
				if(findroot(j,i))//j的父亲/祖宗是i
					day=max(day,caltime2(j,i));
			}
			day=n-day+1;
		}
		printf("%d ",day);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	s[0]=0;
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&p);
		s[i]=p;
	}
	for(int i=1;i<=m;i++)
		scanf("%d",&t[i]);
	solve(1);
	if(cmplt)
	{
		printf("\n");
		solve(2);
	}
	return 0;
}

评测结果
在这里插入图片描述


三、JPEG解码

1.题目内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.题解分析

个人觉得是个小模拟。为什么这样说呢,因为题目把操作的步骤一步步说清楚了,计算的公式也给齐了,数据量也不大,8x8矩阵,所以O( n 2 n^2 n2)计算完全没问题,按部就班就行了。

主要难点有:
1.z形读入数据;
2.离散余弦逆变换计算。

首先来讲讲z形读入。

观察每条斜着的路径,会发现坐标会按如下规律变化:

x--,y++;//右上角方向
x++,y--;//左下角方向

然后运动到边缘,有个平移,然后转向操作,所以我们声明个方向标记down,当在边缘时,平移一下并改变方向标记。根据方向来决定如何移动;

但这样只能完成一个三角形的z形读入,即矩形一半(用左下角与右上角连接的对角线分割)。再观察,发现在上下半部分切换时平移方法会改变,所以增加一个上部分标记upart,根据upart来决定到边缘时如何平移,再用左下角角的坐标判定上下部分切换,改变upart值即可。

if(upart&&!down&&x==0)
			y++,down=1;
		else if(upart&&down&&y==0)
			x++,down=0;
		else if(!upart&&!down&&y==7)
			x++,down=1;
		else if(!upart&&down&&x==7)
			y++,down=0;
		else if(down)
			x++,y--;
		else 
			x--,y++;
		if(x==7&&y==0)
			upart=0;

然后是离散余弦逆变换。根据这个公式计算:
在这里插入图片描述
这个公式的意思即对于每个 M i , j ′ M'_{i,j} Mi,j,把i,和j代入,然后对u和v从0到7的值按公式求和。
理解之后直接代入即可。

double cal(int i,int j)
{
	double res=0.0,pi=acos(-1),a=sqrt(1/2.0);
	for(int u=0;u<8;u++)
		for(int v=0;v<8;v++)
			res+=(u==0?a:1.0)*(v==0?a:1.0)*m[u][v]*cos(pi/8.0*(i+0.5)*(u*1.0))*cos(pi/8.0*(j+0.5)*(v*1.0));
	return 0.25*res;
}

最后再说下四舍五入,我的做法是获取其小数部分,判断它是否大于0.5,是就输出整数部分+1,否就直接输出整数部分

double c=num-(int)num;
if(c<0.5)
	printf("%d ",(int)num);
else
	printf("%d ",(int)num+1);

3.完整代码

#include<bits/stdc++.h>
using namespace std;
int n,T;
double q[10][10];
double m[10][10];
void print()
{
	for(int i=0;i<8;i++)
	{
		for(int j=0;j<8;j++)
			printf("%d ",(int)m[i][j]);
		printf("\n");
	}
}
double cal(int i,int j)
{
	double res=0.0,pi=acos(-1),a=sqrt(1/2.0);
	for(int u=0;u<8;u++)
		for(int v=0;v<8;v++)
			res+=(u==0?a:1.0)*(v==0?a:1.0)*m[u][v]*cos(pi/8.0*(i+0.5)*(u*1.0))*cos(pi/8.0*(j+0.5)*(v*1.0));
	return 0.25*res;
}
int main()
{
	for(int i=0;i<8;i++)
		for(int j=0;j<8;j++)
			scanf("%lf",&q[i][j]);
	scanf("%d",&n);
	scanf("%d",&T);
	//z形写入矩阵m
	int x=0,y=0;
	bool down=0,upart=1;
	for(int k=0;k<n;k++)
	{
		double s;
		scanf("%lf",&s);
		m[x][y]=s;
		if(upart&&!down&&x==0)
			y++,down=1;
		else if(upart&&down&&y==0)
			x++,down=0;
		else if(!upart&&!down&&y==7)
			x++,down=1;
		else if(!upart&&down&&x==7)
			y++,down=0;
		else if(down)
			x++,y--;
		else 
			x--,y++;
		if(x==7&&y==0)
			upart=0;
	}
	if(T==0)
		print();
	else
	{
		for(int i=0;i<8;i++)
			for(int j=0;j<8;j++)
				m[i][j]*=q[i][j];
		if(T==1)	
			print();
		else
		{
			for(int i=0;i<8;i++)
			{
				for(int j=0;j<8;j++)
				{
					double num=cal(i,j)+128;
					if(num>255)
						printf("255 ");
					else if(num<0)
						printf("0 ");
					else
					{
						double c=num-(int)num;
						if(c<0.5)
							printf("%d ",(int)num);
						else
							printf("%d ",(int)num+1);
					}
				}
				printf("\n");
			}
		}
	}
	return 0;
}

评测结果
在这里插入图片描述


都看到这里了,能否给个小小的赞呢?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值