CSP 202212题解:现值计算,训练计划,JPEG 解码,聚集方差,星际网络

试题内容请前往CCF官网查看:

CCF-CSP计算机软件能力认证考试
http://118.190.20.162/home.page

这是一份以C++代码编写的CSP 专业组 202212题解。

阅读本题解前,您应当了解下列知识:

  1. 教程
  2. 树链剖分(轻重链剖分) 教程
  3. 树上启发式合并 教程

请注意这是CSP-S/J的中学生竞赛的题解。

现将模拟测试系统中的得分列举如下:

题目得分时间内存
现值计算10015ms3.136MB
训练计划10015ms2.988MB
JPEG 解码10015ms2.921MB
聚集方差1001.828s192.9MB
星际网络---

最后一题还没有看,日后更新。

1. 现值计算

提示很充足,按照提示完成题目即可。

#include<bits/stdc++.h> 
using namespace std;

int main(){
	int n;double s,r,x;
	cin>>n>>r>>s;
	for(int i=1;i<=n;++i){
		cin>>x;
		s+=x*pow(1+r,-i);
	}
	cout<<s<<endl;
	return 0;
}

2. 训练计划

训练计划构成一棵树。最早开始时间从根往叶子递推。最迟开始时间从从叶子往根递推,也就递归求解。

#include<bits/stdc++.h> 
using namespace std;

const int MAXN=370,MAXM=110;
bool vis[MAXN];
int early[MAXM],late[MAXM],p[MAXM],t[MAXM],n;
vector<int> gl[MAXM];

int check_early(int i){
	if(early[i]) return early[i];
	if(p[i]==0) return early[i]=1;
	return early[i]=check_early(p[i])+t[p[i]];
}

int check_late(int i){
	if(i==0) return n+1;
	if(late[i]<=n) return late[i];
	int lt=n+1;
	for(auto ch:gl[i]){lt=min(lt,check_late(ch));}
	return late[i]=lt-t[i];
}

int main(){
	int m,x;
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>p[i];
		if(p[i])gl[p[i]].push_back(i);
	}
	for(int i=1;i<=m;++i)cin>>t[i],late[i]=n+1;
	late[0]=n+1;
	for(int i=1;i<=m;++i)check_early(i);
	for(int i=1;i<=m;++i)cout<<early[i]<<" ";
	cout<<endl;
	bool f=true;
	for(int i=1;i<=m;++i)
		if(check_late(i)<=0){
			f=false;break;
		}
	if(f){
		for(int i=1;i<=m;++i)cout<<late[i]<<" ";
		cout<<endl;
	}
	return 0;
}

3. JPEG 解码

大型模拟题,没有什么难度,照做即可。

值得一提的是蛇形输入,这个稍微动动脑筋。实在不行打个表,指示每个数的输入位置,毕竟只有8x8=64个格子。

#include<bits/stdc++.h>
using namespace std;

const double pi8=acos(-1)/8.0;
double alpha(int u){return u==0?sqrt(0.5):1;}
int cast(int x){
	if(x<0) return 0;
	if(x>255) return 255;
	return x;
}

class matrix{
	int a[8][8];
public:
	matrix(){
		for(int i=0;i<8;++i)
			for(int j=0;j<8;++j)
				a[i][j]=0;
	}
	void read_mat(){
		for(int i=0;i<8;++i)
			for(int j=0;j<8;++j)
				cin>>a[i][j];
	}
	void read_scan_data(int n){
		int i=0,j=0,k=1;
		while(n--){
			cin>>a[i][j];
			if(k==1)
				if(j==7) ++i,k=-1;
				else if(i==0) ++j,k=-1;
				else --i,++j;
			else//k==-1
				if(i==7) ++j,k=1;
				else if(j==0) ++i,k=1;
				else ++i,--j;
		}
	}
	matrix& operator*=(matrix &m){
		for(int i=0;i<8;++i)
			for(int j=0;j<8;++j)
				a[i][j]*=m.a[i][j];
		return (*this);
	}
	matrix idct(){
		matrix ret;
		for(int i=0;i<8;++i){
			for(int j=0;j<8;++j){
				double s=0;
				for(int u=0;u<8;++u)
					for(int v=0;v<8;++v)
						s+=alpha(u)*alpha(v)*a[u][v]*cos(pi8*(i+0.5)*u)*cos(pi8*(j+0.5)*v);

				ret.a[i][j]=cast(round(s/4.0+128));
			}
		}
		return ret;
	}
};
int main(){
	matrix q,m;
	q.read_mat();
	int n;cin>>n;
	int T;cin>>T;
	m.read_scan_data(n);
	if(T==0){m.print();return 0;}
	m*=q;
	if(T==1){m.print();return 0;}
	m.idct().print();
	return 0;
}

4. 聚集方差

子任务1,2: 对于一棵子树,我们维护一个map,指示其中每种数有几个,然后计算“聚集方差”即可。向上递推时,暴力合并几个子树再加一个根节点。时间复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn) log ⁡ \log log是由map查询操作带来的),可得40分。

#include<bits/stdc++.h> 
using namespace std;

typedef long long ll;
ll sqr(int x){return (ll)x*x;}
const int MAXN=3e5+10;

struct sta{
	int min_dist,count;
	sta():min_dist(-1),count(1){}
	sta(int md,int cnt):min_dist(md),count(cnt){}
	void inc(int c=1){count+=c;if(count>=2) min_dist=0;}
};

typedef map<int,sta> dict;
typedef dict::iterator dictit;

class statistics{//个数统计器 
	int cnt;ll ga;
	dict mp;
	int __1st() {return mp.begin()->first;}//得到map中的第一个数
	pair<vector<int>,vector<dictit> > __dist(dictit it) {//对于给定的数,计算它的最小距离,以及左右2个数的最小距离
		vector<int> ret;vector<dictit> retp(3,mp.end());
		int x=it->first,d=INT_MAX;
		if(it==mp.begin()){
			ret.push_back(-1);
		}else{
			auto itl=it;--itl;
			if(x-itl->first<d){
				d=x-itl->first;
				retp[2]=itl;
			}
			ret.push_back(itl->second.min_dist);
			retp[0]=itl;
		}
		auto end=mp.end();--end;
		if(it==end){
			ret.push_back(-1);
		}else{
			auto itr=it;++itr;
			if(itr->first-x<d){
				d=itr->first-x;
				retp[2]=itr;
			}
			ret.push_back(itr->second.min_dist);
			retp[1]=itr;
		}
		ret.push_back(d);
		return {ret,retp};//{左侧数的最小距离,右侧数的最小距离,该数的最小距离},{左侧数的迭代器,右侧数的迭代器,最小距离对应数的迭代器}
	}
public:
	statistics(int x):cnt(1),ga(0){mp[x]=sta(-1,1);}
	void insert(int x,int x_cnt){//插入x_cnt个x
		if(cnt==1) {
			int y=__1st(),d=abs(x-y);
			if(d==0) mp[x]=sta(0,2),ga=0;
			else mp[x]=sta(d,1),mp[y]=sta(d,1),ga=2*sqr(d);
			--x_cnt;++cnt;
			if(x_cnt==0) return;
		}
		auto it=mp.find(x);
		if(it!=mp.end()){
			if(it->second.count==1) ga-=sqr(it->second.min_dist);
			it->second.inc(x_cnt);
		}else{
			it=mp.insert({x,sta(-1,x_cnt)}).first;
			auto dsp=__dist(it);
			int dl=dsp.first[0],dr=dsp.first[1],d=dsp.first[2];
			if(x_cnt>1) d=0;
			it->second.min_dist=d;
			auto pl=dsp.second[0],pr=dsp.second[1];
			int l,r;
			if(dl!=-1 && dl>x-(l=pl->first)){
				ga+=sqr(x-l)-sqr(dl);
				pl->second.min_dist=x-l;
			}
			if(dr!=-1 && dr>(r=pr->first)-x){
				ga+=sqr(r-x)-sqr(dr);
				pr->second.min_dist=r-x;
			}
			ga+=sqr(d);
		}
		cnt+=x_cnt;
	}
	void insert(statistics &st){//暴力合并子树
		for(auto p:st.mp)insert(p.first,p.second.count);
	}
	ll get_ga(){return ga;}
};
 
struct node{
	int fa,val;ll ga;
	vector<int> son;
};

class tree{
	int n;
	vector<node> a;
public:
	tree(int _n):n(_n),a(_n+1){}
	void read(){
		int f;
		for(int i=2;i<=n;++i){
			cin>>f;a[i].fa=f;
			a[f].son.push_back(i);
		}
		for(int i=1;i<=n;++i)cin>>a[i].val;
	}
	int sz(int u){return a[u].son.size()+1;}
	int v(int u){return a[u].val;}
	ll operator[](int u){return a[u].ga;}
	statistics solve(int u){
		statistics st(v(u));
		if(sz(u)==1) {
			a[u].ga=0;
			return st;
		}
		for(auto v:a[u].son){
			auto ret=solve(v);
			st.insert(ret);
		}
		a[u].ga=st.get_ga();
		return st;
	}
};

int main(){
	ios::sync_with_stdio(false);
	int n;
	cin>>n;
	tree tr(n);
	tr.read();
	tr.solve(1);
	for(int i=1;i<=n;++i) cout<<tr[i]<<endl;
	return 0;
}

完整解: 上一个方法慢就慢在两个子树合并的时间复杂度为 O ( n ) O(n) O(n),如果我们能够采取某种方法 O ( 1 ) O(1) O(1)或者 O ( log ⁡ n ) O(\log n) O(logn)合并子树,那么问题就解决了。

如果你熟练的掌握树链剖分(这里特指轻重链剖分),那么你应该已经想到了解决方案:

总是以重儿子map为主,然后将轻儿子map合并到重儿子的map上。

证明: 利用轻重链剖分的性质,一个点到根路径上不超过 log ⁡ n \log n logn条轻边,也就是说,一个点最多会被暴力合并 log ⁡ n \log n logn次。

这种方法被称为树上启发式合并。时间复杂度 O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n)

事实上,相比40分的代码,以下代码没有很大的改动,仅仅增加了dfs1函数用于寻找重儿子,并对solve递归函数进行了简要修改:

#include<bits/stdc++.h> 
using namespace std;

typedef long long ll;
ll sqr(int x){return (ll)x*x;}
const int MAXN=3e5+10;

struct sta{
	int min_dist,count;
	sta():min_dist(-1),count(1){}
	sta(int md,int cnt):min_dist(md),count(cnt){}
	void inc(int c=1){
		count+=c;
		if(count>=2) min_dist=0;
	}
};

typedef map<int,sta> dict;
typedef dict::iterator dictit;

class statistics{//个数统计器 
	int cnt;ll ga;
	dict mp;
	int __1st() {return mp.begin()->first;}
	pair<vector<int>,vector<dictit> > __dist(dictit it) {
		vector<int> ret;vector<dictit> retp(3,mp.end());
		int x=it->first,d=INT_MAX;
		if(it==mp.begin()){
			ret.push_back(-1);
		}else{
			auto itl=it;--itl;
			if(x-itl->first<d){
				d=x-itl->first;
				retp[2]=itl;
			}
			ret.push_back(itl->second.min_dist);
			retp[0]=itl;
		}
		auto end=mp.end();--end;
		if(it==end){
			ret.push_back(-1);
		}else{
			auto itr=it;++itr;
			if(itr->first-x<d){
				d=itr->first-x;
				retp[2]=itr;
			}
			ret.push_back(itr->second.min_dist);
			retp[1]=itr;
		}
		ret.push_back(d);
		return {ret,retp};//{左侧数的最小距离,右侧数的最小距离,该数的最小距离},{左侧数的迭代器,右侧数的迭代器,最小距离对应数的迭代器}
	}
public:
	statistics(int x):cnt(1),ga(0){mp[x]=sta(-1,1);}
	void insert(int x,int x_cnt=1){
		if(cnt==1) {
			int y=__1st(),d=abs(x-y);
			if(d==0) mp[x]=sta(0,2),ga=0;
			else mp[x]=sta(d,1),mp[y]=sta(d,1),ga=2*sqr(d);
			--x_cnt;++cnt;
			if(x_cnt==0) return;
		}
		auto it=mp.find(x);
		if(it!=mp.end()){
			if(it->second.count==1) ga-=sqr(it->second.min_dist);
			it->second.inc(x_cnt);
		}else{
			it=mp.insert({x,sta(-1,x_cnt)}).first;
			auto dsp=__dist(it);
			int dl=dsp.first[0],dr=dsp.first[1],d=dsp.first[2];
			if(x_cnt>1) d=0;
			it->second.min_dist=d;
			auto pl=dsp.second[0],pr=dsp.second[1];
			int l,r;
			if(dl!=-1 && dl>x-(l=pl->first)){
				ga+=sqr(x-l)-sqr(dl);
				pl->second.min_dist=x-l;
			}
			if(dr!=-1 && dr>(r=pr->first)-x){
				ga+=sqr(r-x)-sqr(dr);
				pr->second.min_dist=r-x;
			}
			ga+=sqr(d);
		}
		cnt+=x_cnt;
	}
	void insert(statistics &st){
		for(auto p:st.mp)insert(p.first,p.second.count);
	}
	ll get_ga(){return ga;}
};
 
struct node{
	int fa,val;ll ga;
	vector<int> son;
	int sz,hson;
};

class tree{
	int n;
	vector<node> a;
	void dfs1(int u,int f) { //寻找重儿子 
	    a[u].sz=1;
	    for(auto v:a[u].son) {
	        if(v==f)continue;
	        dfs1(v,u);
	        a[u].sz+=a[v].sz;
	        if(a[a[u].hson].sz<a[v].sz) a[u].hson=v;
	    }
	}
public:
	tree(int _n):n(_n),a(_n+1){}
	void read(){
		int f;
		for(int i=2;i<=n;++i){
			cin>>f;a[i].fa=f;
			a[f].son.push_back(i);
		}
		for(int i=1;i<=n;++i)cin>>a[i].val;
		dfs1(1,0);
	}
	int sz(int u){return a[u].son.size()+1;}
	int hson(int u){return a[u].hson;}
	int v(int u){return a[u].val;}
	ll operator[](int u){return a[u].ga;}
	statistics solve(int u){
		if(sz(u)==1) {
			a[u].ga=0;
			return statistics(v(u));
		}
		statistics st=solve(hson(u));
		for(auto v:a[u].son){
			if(v==hson(u))continue; 
			auto ret=solve(v);
			st.insert(ret);
		}
		st.insert(v(u));
		a[u].ga=st.get_ga();
		return st;
	}
};

int main(){
	ios::sync_with_stdio(false);
	int n;
	cin>>n;
	tree tr(n);
	tr.read();
	tr.solve(1);
	for(int i=1;i<=n;++i) cout<<tr[i]<<endl;
	return 0;
}

5. 星际网络

(日后更新)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值