信息学联赛模拟题3+解析+代码
一.题目概况
中文题目名称 | 生日礼物 | 平均任务 | 买玩具 | 每条边的最小生成树 |
---|---|---|---|---|
英文题目与子目录名 | 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;
}