【CCF】全国信息学奥林匹克联赛模拟题+解析+代码

一.题目概况

中文题目名称 生日礼物 平均任务 买玩具 每条边的最小生成树
英文题目与子目录名 gift task buy mst
可执行文件名 gift task buy mst
输入文件名 gift.in task.in buy.in mst.in
输出文件名 gift.out task.out buy.out mst.out
每个测试点时限 1 秒 1 秒 1 秒 1 秒
测试点数目 10 10 10 10
每个测试点分值 10 10 10 10
附加样例文件
结果比较方式 全文比较(过滤行末空格及文末回车) 全文比较(过滤行末空格及文末回车) 全文比较(过滤行末空格及文末回车) 全文比较(过滤行末空格及文末回车)
题目类型 传统 传统 传统 传统
运行内存上限 128M 128M 128M 128M

二.提交源程序文件名

对于 C++语言 gift.cpp task.cpp buy.cpp mst.cpp
对于 C 语言 gift.c task.c buy.c mst.c
对于 Pascal 语言 gift.pas task.pas buy.pas mst.pas

三.编译命令(不包含任何优化开关)

对于 C++语言 g++ -o gift g++ -o task g++ -o buy g++ -o mst
gift.cpp -lm task.cpp -lm buy.cpp -lm mst.cpp -lm

对于 C 语言 gcc -o gift gift.c gcc -o task task.c gcc -o buy gcc -o mst
-lm -lm buy.c -lm mst.c -lm

对于 pascal 语言 fpc gift.pas fpc task.pas fpc buy.pas fpc mst.pas

注意事项:

1、文件名(程序名和输入输出文件名)必须使用英文小写。
2、C/C++中函数 main()的返回值类型必须是 int,程序正常结束时的返回值必须是 0。
3、全国统一评测时采用的机器配置为:CPU AMD Athlon™ II x2 240 processor,2.8GHz,内存 4G,上述时限以此配置为准。
4、只提供 Linux 格式附加样例文件。
5、特别提醒:评测在当前最新公布的 NOI Linux 下进行,各语言的编译器版本以其为准。

1、生日礼物

(gift.cpp/c/pas)

【问题描述】

下周就是小光的生日了,小明决定买一个礼物送给他。小明知道小光很喜欢看书,所以他决定去书店买书。书店共有 n 本不同的书,这些书可以分为 m 种类型。
小明决定买两本不同类型的书送给小光。
现在请你来计算有多少种不同的买书方法,注意只要有一本书不同,我们就认为是不同的方法。

【输入格式】

输入文件名为 gift.in。
输入文件第一行是一个正整数 n 和 m,分别书的数量和种类。第二行包含 n 个数,a1,a2,a3…an,表示每本书的类型,数据保证每种类型的书至少有一本。

【输出格式】

输出文件名为 gift.out。
输出文件只有一行一个整数,表示小明选书的方案总数。保证答案不超过 2*10^9。

【输入输出样例 1】

gift.in

4 3
2 1 3 1

gift.out

5

【输入输出样例 1 说明】

共有 5 种不同的选书方法,每种分别是(第 1 本书,第 2 本书),(第 1 本书,第 3 本书),(第 1 本书,第 4 本书),(第 2 本书,第 3 本书),,(第 3 本书,第 4 本书)。第 2 本和第4 本不能同时选,因为类型是一样的。

【输入输出样例 2】

gift.in

7 4
4 2 3 1 2 4 3

gift.out

18

【输入输出样例 2 说明】

共有 18 种选法。

【数据说明】

2<=n<=200000,2<=m<=10,1<=ai<=m。

【解析】

简单题,时间复杂度 O(n)。

【代码】

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

int cnt[13],n,m;

int main() {
	//freopen("gift.in","r",stdin);
	//freopen("gift.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=0; i<n; i++) {
		int k;
		scanf("%d",&k);
		cnt[k]++;
	}
	int ans=0;
	for(int i=1; i<m; i++) for(int j=i+1; j<=m; j++)
		ans+=cnt[i]*cnt[j];
	printf("%d\n",ans);
	return 0;
}

2、平均任务

(task.cpp/c/pas)

【问题描述】

学校的机房里有 n 台服务器负责处理各种各样的任务,每台服务器需要处理多少任务你是知道的,第 i 台服务器需要处理 mi 个任务。
现在,为了平衡各个服务器之间的任务数量,你可以把某些服务器上的任务放到另外的服务器上处理,最终的目的,我们要使得 ma-mb 最小,ma 是任务最多的那个服务器的任务数量,mb 是任务最少的那个服务器的任务数量。
每一秒钟,你只能重新分配一个任务(即把一个任务从一台服务器放到另一台服务器),现在请你写一个程序,为了使得 ma-mb 最小,最少需要多少时间才能把这些任务重新分配好。

【输入格式】

输入文件名为 task.in。
输入文件第一行是一个正整数 n,表示服务器的数量,接下来 n 行,每行一个正整数,表示 mi,表示每台服务器上的任务数量。

【输出格式】

输出文件名为 task.out。
输出文件只有一行一个整数,表示最少需要的时间数量(用秒计算)。

【输入输出样例 1】

task.in

2 1
6

task.out

2

【输入输出样例 1 说明】

共需 2 秒,每一秒钟把服务器 1 上的一个任务放到服务器 2 上,最后两台服务器上的任务一样。

【输入输出样例 2】

task.in

7
10 11 10 11 10 11 11

task.out

0

【输入输出样例 2 说明】

已经达到差距最小的情况,所以无需重新分配任务。

【输入输出样例 3】

task.in

5 1
2 3 4 5

task.out

3

【输入输出样例 3 说明】

前两秒,把服务器 5 上面的两个任务放到服务器 1 上。第三秒,把服务器 4 上的一个任务放到服务器 2 上。

【数据说明】

1<=n<=100000,0<=mi<=20000。

【解析】

贪心题,我们只要先安排好目标状态(所有任务数量累加,然后平均分
配,多出来的任务最后的人每人一个任务),对于当前状态,也要从小到大排序,然后从小到大逐一进行比较,sum=累加 abs(a[i]-b[i]),a[i]和 b[i]一个是目标状态,一个是初始状态,最后答案就是 sum div 2,时间复杂度O(nlogn).

【代码】

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

int n,a[100003],b[100003];

int main() {
	//freopen("task.in","r",stdin);
	//freopen("task.out","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++) scanf("%d",&a[i]);
	int sum=0;
	for(int i=1; i<=n; i++) sum+=a[i];
	sort(a+1,a+n+1);
	for(int i=1; i<=n; i++) b[i]=sum/n;
	for(int i=n-sum%n+1; i<=n; i++) b[i]++;
	int ans=0;
	for(int i=1; i<=n; i++) {
	if (a[i]>b[i]) break;
		ans+=b[i]-a[i];
	}
	printf("%d\n",ans);
	return 0;
}

3、买玩具

(buy.cpp/c/pas)

【问题描述】

商店里有 m 个玩具,小光想要买 k 个玩具(m>=k),但是他现在手头上只有 s 个伯乐币,他可以用伯乐币来换取美元或者英镑,每个玩具只能用美元或者英镑其中一种货币购买。
小光可以用 n 天的时间来购买 k 个玩具,每天,你都可以知道伯乐币与美元、英镑的汇率,于是你就可以知道每天美元和英镑的价格了。
每天,小光可以买一个或者多个玩具(当然要在当天的汇率下购买),每个玩具只能被小光买一次,不能重复购买。
请帮助小光计算他最少需要多少天,才能买到 k 个玩具,小光从不在手头上存储美元或者英镑,他只是在要买玩具的时候,才把伯乐币换成对应需要的货币。

【输入格式】

输入文件名为 buy.in。
输入文件第一行是 4 个整数 n,m,k,s,n 表示总共的天数,m 表示玩具的总数,k 表示小光要买的玩具数量,s 表示小光拥有的伯乐币的数量。接下 n 行,每行一个正整数 ai,表示第 i 天换取 1 美元所需要的伯乐币的数量,接下来 n 行,每行一个正整数 bi,表示第 i 天换取 1 英镑所需要的伯乐币的数量,接下来 m 行,每行两个正整数 ti 和 ci,ti 表示第 i 个玩具可以被买的货币类型,如果是 1,表示只能用美元购买,如果是 2,表示只能用英镑购买,
ci 表示购买第 i 个玩具所需要的相应货币的数量。

【输出格式】

输出文件名为 buy.out。
输出文件只有一行一个整数,表示最少要多少天小光才能买到 k 个玩具,如果买不到,就输出-1。

【输入输出样例 1】

buy.in

5 4 2 2
1 2 3 2 1
3 2 1 2 3
1 1
2 1
1 2
2 2

buy.out

3

【输入输出样例 1 说明】

小光在第 1 天用一个伯乐币换取 1 美元,买了 1 个玩具(这个玩具的序号是 1),在第 3 天用 1 个伯乐币换取了 1 英镑,买了 1 个玩具(这个玩具的序号是 2)。

【输入输出样例 2】

buy.in

4 3 2 200
69 70 71 72
104 105 106 107
1 1
2 2
1 2

buy.out

-1

【输入输出样例 2 说明】

由于小光的伯乐币数量不够,所以不能购买 k 个玩具。

【输入输出样例 3】

buy.in

4 3 1 1000000000
900000 910000 940000 990000
990000 999000 999900 999990
1 87654
2 76543
1 65432

buy.out

-1

【输入输出样例 3 说明】

由于小光的伯乐币数量不够,所以不能购买 k 个玩具。

【数据说明】

1<=n<=100000,1<=k<=m<=100000,1<=s<=109,1<=ai<=106,1<=bi<=106,1<=ti<=2,1<=ci<=106

【解析】

贪心,二分枚举答案。这题的本质是二分枚举答案,我们定义 l 为 1,r 为
n+1,如果最后 l 和 r 相等的时候,r 是 n+1,那么就没有答案(也就是-1)。对于任何一个 mid(天数),我们要贪心,把第 1 天到第 mid 天中最小的美元和英镑汇率分别求出(也可以事先预处理),然后对于 m 件玩具,事先预处理按美元和英镑从小到大排序,运用两个指针的算法就可以在 O(K)的时间内求出买 K 个玩具的最小代价,如果这个最小代价小于等于 s,那么 r=mid,否则 l=mid+1。时间复杂度 O(nlogn)。

【代码】

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

struct node {
	LL v;
	int id,day;
} b[200003];

int n,m,k,s,a[200003][2],f[3],t[200003],cost[200003];

bool cmp(node x,node y) {
	return x.v<y.v;
}

bool check(int mid) {
	f[1]=f[2]=1;
	for(int i=2; i<=mid; i++) {
		if(a[i][1]<a[f[1]][1]) f[1]=i;
		if(a[i][2]<a[f[2]][2]) f[2]=i;
	}
	for(int i=1; i<=m; i++) b[i]=node{(LL)a[f[t[i]]][t[i]]*cost[i],i,f[t[i]]};
	sort(b+1,b+m+1,cmp);
	LL ret=0;
	for(int i=1; i<=k; i++) ret+=b[i].v;
	return ret<=s;
}

int main() {
	//freopen("buy.in","r",stdin);
	//freopen("buy.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&k,&s);
	for(int i=1; i<=n; i++) scanf("%d",&a[i][1]);
	for(int i=1; i<=n; i++) scanf("%d",&a[i][2]);
	for(int i=1; i<=m; i++) scanf("%d%d",&t[i],&cost[i]);
	int l=1,r=n+1;
	while(l<r) {
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
			else l=mid+1;
	}
	if (l>n) puts("-1");
		else printf("%d\n",l);
	return 0;
}

4、每条边的最小生成树

(mst.cpp/c/pas)

【问题描述】

现在给你一个没有重边、没有自环的无向连通图,这个图包含 n 个点和 m 条边,对于每一条边,求出包含这条边的最小生成树。

【输入格式】

输入文件名为 mst.in。
输入文件第一行包含两个正整数 n 和 m,分别表示无向连通图中点的数量和边的数量。
接下来 m 行,每行 3 个正整数 ui,vi 和 wi,表示第 i 条边的两个点和权值(wi)。

【输出格式】

输出文件名为 mst.out。
输出文件有 m 行,每行一个整数,表示包含对应的边的最小生成树的代价。

【输入输出样例】

mst.in

5 7
1 2 3
1 3 1
1 4 5
2 3 2
2 5 3
3 4 2
4 5 4

mst.out

9 
8
11
8 
8 
8
9

【输入输出样例说明】

5 个点,7 条边,1-2 的权值是 3,1-3 的权值是 1…
包含第一条边的最小生成树的代价是 9,包含第二条边的最小生成树的代价是 8…

【数据说明】

1<=n<=1000,1<=m<=10000,1<=ui,vi<=n,1<=wi<=109

【解析】

贪心,最小生成树算法,树结构,最近公共祖先。本题的做法是先求出一棵最小生成树,对于在生成树里面的边,很显然答案就是这个生成树的代价,对于不在生成树里面的边,我们很容易想到这条边加入以后,会在树里面形成一个环,我们只要去掉这个环里的最大边就求出包含这条边的最小生成树了,求环的过程其实就是求最近公共祖先的过程,朴素的算法求一次公共祖先需要 O(n)的时间,也可以通过这题,总的时间复杂度是 O(n^2),不过有兴趣的同学可以研究一下最近公共祖先的倍增算法,可以将本题的时间复杂度优化到 O(nlogn)。

【代码】

#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>

using namespace std;

#define pb push_back

typedef long long LL;

struct node {
	int x,y,d,id;
} edge[200003];

int n,m,cnt;
int f[200003][20],g[200003][20],tin[200003],tout[200003],p[200003];
LL ans[200003];
vector<int> a[200003],cost[200003];
bool vis[200003];

bool cmp(node ta,node tb) {
	return ta.d<tb.d;
}

int getfather(int k) {
	return k==p[k] ? k : p[k]=getfather(p[k]);
}

void dfs(int k,int fa,int len) {
	tin[k]=++cnt,f[k][0]=fa,g[k][0]=len;
	for(int i=1; i<20; i++) {
		int tmp=f[k][i-1];
		f[k][i]=f[tmp][i-1];
		g[k][i]=max(g[k][i-1],g[tmp][i-1]);
	}
	for(int i=0; i<(int)a[k].size(); i++)
		if (fa!=a[k][i]) dfs(a[k][i],k,cost[k][i]);
	tout[k]=++cnt;
}

bool ancestor(int x,int y) {
	return (tin[x]<=tin[y] && tout[y]<=tout[x]);
}

int calc(int x,int anc) {
	if(x==anc) return 0;
	int ret=0;
	for(int i=19; i>=0; i--)
		if (!ancestor(f[x][i],anc)) {
			ret=max(ret,g[x][i]);
			x=f[x][i];
		}
	return max(ret,g[x][0]);
}

int main() {
	//freopen("mst.in","r",stdin);
	//freopen("mst.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++) {
		scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].d); 
		edge[i].id=i;
	}
	sort(edge+1,edge+m+1,cmp);
	for(int i=1; i<=n; i++) p[i]=i;
	LL sum=0;
	for(int k=0,i=1; k<n-1; k++,i++) {
		int fa=getfather(edge[i].x);
		int fb=getfather(edge[i].y);
		if ( p[fa]==p[fb]) {
			k--; continue;
		}
		p[fa]=p[fb]; 
		vis[edge[i].id]=true; 
		sum+=edge[i].d;
		a[edge[i].x].pb(edge[i].y); 
		a[edge[i].y].pb(edge[i].x);
		cost[edge[i].x].pb(edge[i].d); 
		cost[edge[i].y].pb(edge[i].d);
	}
	tin[0]=0; 
	tout[0]=2*n+1;
	dfs(1,0,0);
	for(int i=1; i<=m; i++) if(vis[edge[i].id]) ans[edge[i].id]=sum;
		else {
			int x=edge[i].x,y=edge[i].y,anc;
			if(ancestor(x,y)) anc=x;
				else if(ancestor(y,x)) anc=y;
					else {
						anc=x;
						for(int j=19; j>=0; j--)
						if (!ancestor(f[anc][j],y)) anc=f[anc][j];
						anc=f[anc][0];
					}
			int mx=max(calc(x,anc),calc(y,anc));
			ans[edge[i].id]=sum-mx+edge[i].d;
		}
	for(int i=1;i<=m;i++) printf("%I64d\n",ans[i]);
	return 0;
}

发布了106 篇原创文章 · 获赞 156 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Ljnoit/article/details/104857173