清北学堂2017NOIP冬令营入学测试

P4744 A’s problem(a)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试题,每三天结算一次成绩。参与享优惠

描述

这是一道有背景的题目,小A也是一个有故事的人。但可惜的是这里纸张太小,小A无法把故事详细地说给大家听。可能小A自己也讲不清楚自己的故事,因为如果讲清了,也就没有这道题目了……

小A的问题是这个样子,它找到了n份不同的工作,第i份工作每个月有ai的工资,每份工作需要小A每天工作8小时,一周工作7天。小A想知道性价比最高(一个月的工资除以总时长)的工作的编号是多少。如果有多份,输出编号最小的就可以了。

输入格式

第一行一个数n,表示有n份工作。

接下来n个数表示ai。

输出格式

输出一个数表示答案。

备注

输入样例

5

3 3 4 5 5

输出样例

4

数据范围

对于100%的数据n<=100,1<=ai<=1000。

因为每份工作时长相等,所以性价比=工资/时长 只看工资。所以本题解法为从前往后找最大的第一个数

#include<cstdio>
using namespace std;
int n,maxn,ans,x;
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",&x);
if(x>maxn)
{
maxn=x;
ans=i;
}
}
printf("%d",ans);
return ;
}
P4745 B’s problem(b)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试

描述

题目描述

小B生活在一个很奇怪的国家里,这个国家的钱的面值只有可能是25,50,100的。小B最近在做社会实践,这次它选择在一个餐厅里干这件事情。但今天发生了一件有趣的事,这件事情是这个样子的,餐厅里大家都在排队买饭,粗心的打饭阿姨忘记要带零钱,并且所有排队打饭的人只带了一张钱。

具体地,第i个人带了一张面额为ai的钱,为了方便起见,我们规定每个人都想买价值25元的饭盒。阿姨显得不知所措。聪明的小B想到了一个方法,让带了25元的先买饭!这样阿姨就有了更多的零钱去找开一些面值较大的钱。

但这样对于一些人来说仍有可能找不开零钱,小B想知道是否存在一种排队方案,能够对所有人找开零钱。如果可行输出“YES”,否则输出“NO”。

输入格式

第一行一个数n,表示有n个想买饭的人。

接下来一行n个数ai,表示第i个人带着的钱的面额。

输出格式

输出“YES”或者“NO”。

备注

输入样例

3

25 50 100

输出样例

NO

数据范围

对于100%的数据n<=100,ai=25或者50或者100。

设25、50、100的人分别为s25,s50,s100

1.如果s25<s50,那么25的不够给50的找钱,输出NO

2.在枚举每个s100过程中(也就是s100>0),有50的先找1张50和1张25,没有50的就找3张25的,中途如果出现手中25不够用的情况,那么输出NO

#include<cstdio>
#include<iostream>
using namespace std;
int n,a,b,c,x;//a,b,c分别代表手持25、50、100的人的总数
int aa,bb,cc;//分别代表打饭阿姨手中25、50、100元的数量
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",&x);
if(x==) a++;
else if(x==) b++;
else c++;
}
aa=a;//25元的不用找钱,打饭阿姨手中多了a张25元
aa-=b;//给每个50找25元钱后剩下几张25
bb=b;//打饭阿姨手中多了b张50元
if(aa<)//25元的不够找
{
cout<<"NO";return ;
}
for(int i=;i<=c;i++)//给每个100的找钱
{
if(bb>)//优先找50元
{
bb--;
aa--;//找一张50和一张25
if(aa<)//25的不够找
{
cout<<"NO";return ;
}
}
else if(aa==)//c还没有枚举完,25元的没了
{
cout<<"NO";return ;
}
else if(aa>)//没有50的找3张25
{
aa-=;
if(aa<)//25的不够了
{
cout<<"NO";return ;
}
}
}
cout<<"YES";return ;
}
P4746 C’s problem(c)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试

描述

题目描述

小C是一名数学家,由于它自制力比较差,经常通宵研究数学问题。

这次它因为这个数学问题已经两天两夜没有睡觉了,再不研究出来就要出人命了!快帮帮它吧!

这个问题是这样的,有一个数n,将其拆分成若干自然数之和,要求乘积最大!

如果你以为问题仅仅这么简单,那你就太naive了。

由于小C挑战自己的自我修养,它规定分成的自然数两两之间一定不能相等

它请你输出这个乘积最大是多少,但这个答案太大了,小C并没有兴趣看那么长的数字,它只想知道这个数对1000000007取模后的值是多少。

输入格式

一行一个数表示n

输出格式

一个数表示答案

备注

输入样例

6

输出样例

8

数据范围

对于30%的数据n<=10。

对于50%的数据n<=10000。

对于100%的数据1<=n<=1000000000。

30分暴力

#include<iostream>
#include<cstdio>
#define mod 1000000007
using namespace std;
int n;
long long ans;
bool v[];
void dfs(int now,int remain,long long s)//now当前分出来的数,remain还剩下多少,s乘积
{
if(remain==)
{
ans=max(s,ans);
return ;
}
for(int i=now+;i<=remain;i++)
{
if(!v[i])//不能有重复
{
v[i]=true;
dfs(i,remain-i,s*i%mod);
v[i]=false;
}
}
}
int main()
{
scanf("%d",&n);
if(n==)//1不用拆
{
cout<<;
return ;
}
dfs(,n,);
if(ans==n) cout<<ans-;//2、3、4的结果应该是1、2、3,但dfs结果是2,3,4,因为拆分成了0和本身。dfs时从1开始,所以2,3,4的0乘本身算成了本身。>4的数答案大于本身,所以不用管
else cout<<ans;
}
AC做法:
本题不难,难就难在找规律。
我们设待拆分数为n
看:
5=2*3
6=2*4  4是3+1得到的
7=3*4  3是2+1得到的
8=3*5  5是4+1得到的
9=2*3*4  数的个数增加1个,第1个数变成从2开始
10=2*3*5   5是4+1得到的
11=2*4*5 4是3+1得到的
12=3*4*5  3是2+1得到的
13=3*4*6  6是5+1得到的
14=2*3*4*5  数的个数增加1个,第1个数变成从2开始
由此得到第一个规律,对于数n,要么是n-1中的某一个数+1,要么是数的个数+1,第1个数变成2,往后递增
那么,什么时候对前一个数+1,什么时候数的个数+1呢?
我们再看:
本题要求乘积最大,设2个数a,b,且a<b,那么(a+1)*b=a*b+a,a*(b+1)=a*b+b;所以(a+1)*b<a*(b+1)。
由此可以得出结论,若给拆分后的数+1,那么最优解应该是给大的数+1
继续观察规律:
9拆成2*3*4,3个数
10:4(第3个数)+1
11:3(第2个数)+1
12:2(第1个数)+1
13:5(第3个数)+1
所以第二个规律:对于数n-1得到n时,给某个数+1是倒着来加的(语言表达能力有限,不懂请留言)
接下来,什么时候是数的个数+1呢?
5是2个数的开始,拆成2*3
9是3个数的开始,拆成2*3*4
14是4个数的开始,拆成2*3*4*5
由此可以推出
20是5个数的开始,拆成2*3*4*5*6
所以第三个规律:对于数n,若是可以拆成2*3*4*5*6……的形式,那个到n的最优解就是数的个数+1,第1个数变成2,往后递增
为什么我是从5开始找规律呢?因为当n<=4时,在拆出的数不相等的条件下,其本身最大,可以自己验证。
所以第四个规律:当n<=4时,答案就是本身
由以上4个规律,得到最终算法:
0、if(n<=4) cout<<n,结束
1、把数n从2开始分,拆成2*3*4*5……,直至拆到不够拆为止
2、如果n恰好拆完,那么答案就是2*3*4*5*6……,结束程序
3、    如果n拆不完,设n拆出的数分别为a(1)、a(2)、a(3)、a(4)……a(m)。
         将剩下的数从a(m)开始,依次给a(m)加1、a(m-1)加1、a(m-2)……a(1)加1,直至加到n剩下的数全都加进去。若加到a(1),n剩下的数还没加完,      则再从a(m)开始。
4、最终答案就是a(1)*a(2)*a(3)*……a(m)
#include<cstdio>
#define mod 1000000007
using namespace std;
int n,now=,a[],cnt;
long long ans=;
int main()
{
scanf("%d",&n);
if(n<=)
{
printf("%d",n);
return ;
}
while(n>=now)
{
a[++cnt]=now;
n-=now;
now++;
}
if(!n)
{
for(int i=;i<=cnt;i++)
ans=a[i]%mod*ans%mod;
printf("%d",ans);
return ;
}
int tot=cnt;
while(n)
{
if(!tot) tot=cnt;
a[tot--]++;
n--;
}
for(int i=;i<=cnt;i++)
ans=a[i]%mod*ans%mod;
printf("%d",ans);
return ;
}
P4747 D’s problem(d)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试题

描述

题目描述

小D是一名魔法师,它最喜欢干的事就是对批判记者了。

这次记者招待会上,记者对于小D的数学很好奇。于是小D找了个方法把记者批判了一番。

它对记者抛出了这么一个问题:我有n点能量,写下数字i(1<=i<=9)需要花费a{i}点能量,我用这n点能量最多能写出什么数来?(当然可以不用光n点能量,具体看样例)

记者们一脸懵逼,于是来求助于你。

输入格式

一行10个数,表示n,a1,a2,a3,…,a9。

输出格式

一个数表示答案。

备注

输入样例1

10 2 2 1 2 2 2 2 2 2

输出样例1

3333333333

输入样例2

10 4 11 11 11 11 11 11 11 10

输出样例2

11

数据范围

对于30%的数据n,ai<=10。

对于60%的数据n,ai<=100。

对于100% 的数据1<=n,ai<=1000000,n>=min{ai}。

数的大小首先看位数,其次看最高位,然后是次高位……

位数为总能量/花费能量最小的数

设花费能量最小的数为a,花费能量s,答案至少为s/a位s。如果有多个花费能量最小,取数最大的那个

然后用总能量%s,得出还剩下多少能量值。即还有多少能量可以在让答案位数不变的前提下,变得更大。

然后从9开始倒着枚举,如果剩下的能量足够更新一个,那就让最高位更新,其次是次高位,以此类推。

枚举结束条件:1、枚举到的数<=a   2、当前步骤剩下的能量不能更新任何一个数

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n,minn=0x7fffffff,d;
int ans[];
struct node
{
int w;//写下p需要w点能量
int p;
}a[];
bool cmp(node x,node y)//从9——1排序
{
return x.p>y.p;
}
int main()
{
scanf("%d",&n);
for(int i=;i<=;i++)
{
scanf("%d",&a[i].w);
a[i].p=i;
if(a[i].w<=minn)//minn当前耗费最小的能量
{
minn=a[i].w;
d=a[i].p;//耗费能量minn写下的最大的数
}
}
int t=n%minn,tt=n/minn;//tt:ans的位数,t:写下tt位d后剩下的能量
sort(a+,a+,cmp);//从9——1排序
//sort(a+1,a+10,greater<int>());
int k=;//当前可更新的最高位
for(int i=;i<=tt;i++) ans[i]=d;//写下tt位d
while(t)
{
bool ok=false;//ok起的作用:当当前步骤剩下的能量不能更新任何一个数时,退出
for(int i=;i<=;i++)//将此处循环改成从9——1,把结构体改成数组a[i]=j代表写下i用j点能量,可以省去前面sort排序
if(a[i].p<=d) break;//a[i].p从大到小排序,如果当前的p小于d,后面的一定小于d,更新会使ans更小。如果p等于d。没有更新必要
else if(a[i].w-minn<=t)//能量可以将d更新成a[i].p
{
ans[k++]=a[i].p;//先更新,k再++
t=t-(a[i].w-minn);
ok=;
break;
}
if(ok) continue;
else break;
}
for(int i=;i<=tt;i++) cout<<ans[i];
}
P4748 E’s problem(e)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试

描述

题目描述

小B生活在一个很奇怪的国家里,这个国家的钱的面值只有可能是25,50,100的。小B最近在做社会实践,这次它选择在一个餐厅里干这件事情。但今天发生了一件有趣的事,这件事情是这个样子的,餐厅里大家都在排队买饭,粗心的打饭阿姨忘记要带零钱,并且所有排队打饭的人只带了一张钱。

具体地,第i个人带了一张面额为ai的钱,为了方便起见,我们规定每个人都想买价值25元的饭盒。阿姨显得不知所措。聪明的小B想到了一个方法,让带了25元的先买饭!这样阿姨就有了更多的零钱去找开一些面值较大的钱。

但这样对于一些人来说仍有可能找不开零钱,小B想知道是否存在一种排队方案,能够对所有人找开零钱。

但这个故事是关于小E的。

所以它并不关心能否有这么一种排队方案,它关心的是存在多少这样的排队方案。对于两个持有25元纸币的人,我们认为他们两个人交换位置仍然是同一种排队方案。(也就是说持有同一种纸币的人都可以看作相同的人)

由于答案很大,你只需输出答案对1000000007取模后的结果就可以了。

输入格式

第一行一个数n,表示有n个想买饭的人。

接下来一行n个数ai,表示第i个人带着的钱的面额。

输出格式

输出一个数表示答案。

备注

输入样例

5

25 25 25 50 100

输出样例

5

数据范围

对于30%的数据n<=8。

对于60%的数据n<=20。

对于100%的数据n<=40,ai=25或者50或者100。

60分暴力

#include<cstdio>
using namespace std;
int n,ga,gb,gc,x,s;
void dfs(int now,int aa,int bb,int cc,int a,int b,int c)
//now排完几个人,aa,bb,cc当前卖菜阿姨手里的25、50、100元,a,b,c当前还有几个持有25、50、100的人没排队
{
if(now==n)
{
s++;//排完最后一个人了
s%=;
return;
}
if(a>)//还有手持25元的
{
dfs(now+,aa+,bb,cc,a-,b,c);//排上队,卖菜阿姨手里多了1张25,手持25的人减1个
}
if(b>&&aa>)//还有手持50元的,同时满足卖菜阿姨能找开钱
{
dfs(now+,aa-,bb+,cc,a,b-,c);//排上队,找钱,减1张25;多一张50,手持50的人减1
}
if(c>)//还有手持100的
{
int f=;
if(bb>&&aa>) f=;//卖菜阿姨有50找钱先找50的
else if(aa>=) f=;//没有50的找钱找3张25
if(f==) dfs(now+,aa-,bb-,cc+,a,b,c-);//找钱找1张50,1张25,买菜阿姨手里多1张100,100的人减1
else if(f==) dfs(now+,aa-,bb,cc+,a,b,c-); //找钱找3张25,减3张25,多1张100,100的人减1
} }
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",&x);
if(x==) ga++;//分别统计25、50、100的有几张
else if(x==) gb++;
else gc++;
}
dfs(,,,,ga,gb,gc);
printf("%d",s);
}
P4749 F’s problem(f)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试

描述

这个故事是关于小F的,它有一个怎么样的故事呢。

小F是一个田径爱好者,这天它们城市里正在举办马拉松比赛,这个城市可以被看作是n个点m条带权有向边组成的图。马拉松比赛的终点只有一个:点S。

有k个人参加了这场马拉松,小F所在的城市的马拉松与正常的马拉松不一样,每个人的起点都是不相同的,具体地,第i个人从第{ai}个城市出发,并且第i个人的速度是{vi}。每个人当然是会沿着最短路跑到S点,如果一个人跑步的距离是s,速度是v,那么他所花费的时间为s/v。

现在小F想知道,谁是最快到达终点的。若有多个同时到达终点的,就求跑的路最长的,如果有多个同时到达终点且跑的路最长的,就求编号最大的。

小F想知道那个人的编号是多少。

输入格式

第一行3个数字,n,k,m,表示点的个数,跑步的人数,以及路径的条数。

接下来一行m行,每行3个数ai,bi,ci表示有一条从ai到bi长为ci的有向路径。

接下来一行一个数S。

接下来一行k个数,表示每个人的起点xi。

接下来一行k个数,表示每个人的速度vi。

输出格式

输出一个数表示答案。

测试样例1

输入

5 2 10 
5 1 9 
1 2 81 
2 3 30 
2 1 46 
1 4 45 
2 4 48 
5 1 93 
2 5 61 
2 5 21 
3 5 45 

3 5 
18 29

输出

2

备注

输入样例

3 2 3

1 2 2

1 3 3

2 3 1

3

2 1

1 3

输出样例

2

数据范围

对于30%的数据n<=5,m<=10。

对于100%的数据n<=300,m<=5000。0<=ci<=100,1<=xi,S<=n,1<=vi<=100,1<=k<=n。

最短路问题,算出每个起点到终点的最短路,然后再算时间

堆优化的dijkstra代码:

#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
using namespace std;
int n,k,m,x,y,z,s;
int start[],speed[];//start存储起点,speed存储终点
double time[];
int minn_time;//花费时间最小的人的编号
int head[];
struct node
{
int next,to,d;
}e[];
int DIS[],cnt;
void add(int u,int v,int w)//链表存储
{
cnt++;
e[cnt].to=v;
e[cnt].d=w;
e[cnt].next=head[u];
head[u]=cnt;
}
struct mon
{
int num,dis;
bool operator < (mon k)const //大根堆改成小根堆
{
return dis>k.dis;
}
};
priority_queue<mon>p;//堆优化的spfa
int main()
{
scanf("%d%d%d",&n,&k,&m);
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(y,x,z);//因为dijkstra是单源最短路径,所以从每个起点到达固定的终点,可以转化成从固定的起点到达每个终点。所以把x,y交换存储链表
}
scanf("%d",&s);
memset(DIS,,sizeof(DIS));//dijkstra初始化
p.push((mon){s,});
DIS[s]=;
while(!p.empty())//dijikstra模板
{
mon now=p.top();
p.pop();
if(DIS[now.num]!=now.dis) continue;
for(int i=head[now.num];i;i=e[i].next)
{
if(DIS[now.num]+e[i].d<DIS[e[i].to])
{
DIS[e[i].to]=DIS[now.num]+e[i].d;
p.push((mon){e[i].to,DIS[e[i].to]});
}
}
}
for(int i=;i<=k;i++) scanf("%d",&start[i]);
for(int i=;i<=k;i++)
{
scanf("%d",&speed[i]);
time[i]=(double)DIS[start[i]]/speed[i];
if(minn_time)
{
double h=time[i]-time[minn_time];//引入h防止精度误差,但本题可以不用考虑,直接用>,==,<也能过
if(h<=0.00001&&h>=)//相等
{
if(DIS[i]>=DIS[start[minn_time]])//距离更大的
minn_time=i;//因为i从小到达枚举,所以更新的i一定比minn_time小
}
else if(h<)//当前的更小
minn_time=i;
}
else minn_time=i;
}
printf("%d",minn_time);
}

floyed代码:

#include<iostream>
using namespace std;
int a[][];
int n,k,m,s;
int x,y,z;
int xi[],vi[];
double minx=;
int jl,jlk;
int main()
{
cin>>n>>k>>m;
for(int i=;i<=n;i++)
for(int j=;j<=n;j++)
if(a[i][j]==&&i!=j) a[i][j]=;
for(int i=;i<=m;i++)
{
cin>>x>>y>>z;
a[x][y]=z;
}
for(int kk=;kk<=n;kk++)
for(int i=;i<=n;i++)
for(int j=;j<=n;j++)
{
if(i!=j&&i!=kk&&j!=kk)
{
a[i][j]=min(a[i][j],a[i][kk]+a[kk][j]);
}
}
cin>>s;
for(int i=;i<=k;i++)
cin>>xi[i];
for(int i=;i<=k;i++)
cin>>vi[i];
for(int i=;i<=k;i++){
if(a[xi[i]][s]*1.0/vi[i]<=minx){
if(a[xi[i]][s]*1.0/vi[i]==minx){
if(a[xi[i]][s]>=a[jlk][s]){
minx=a[xi[i]][s]*1.0/vi[i];
jl=i;
jlk=xi[i];
}
}
else{
minx=a[xi[i]][s]*1.0/vi[i];
jl=i;
jlk=xi[i];
}
}
}
cout<<jl;
}
P4750 G’s problem(g)
时间: 1000ms / 空间: 655360KiB / Java类名: Main

背景

冬令营入学测试

描述

题目描述

小G最近在研究国际象棋一类的问题,这个问题是这样的,在一个棋盘放置若干个国王,使得它们两两不互相攻击,求方案总数。

但这个问题似乎显得非常easy,而且真正的国际象棋棋盘中也绝对不只有国王那么简单,于是它打算加几辆车进去!

具体地,一个国王能够攻击其上、下、左、右、左上、右上、左下、右下这8个位置,一辆车能攻击整行整列上的所有位置。小G想在一个n*n的棋盘上放置若干国王与k辆车,它想知道有多少种方案使得国王之间不互相攻击,车之间不互相攻击,车不攻击国王(也就是说意味着国王可以攻击到车)。

当然方案总数很大,你只需告诉小G答案对1000000007取模后的结果就可以了。

输入格式

两个数n,k

输出格式

输出一个数表示答案。

备注

输入样例

2 0

输出样例

5

数据范围

对于20%的数据n,k<=3。

对于50%的数据n,k<=6。

对于80%的数据n,k<=12。

对于100%的数据n,k<=15。

0分暴力,望指错:

#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
int n,k,ans,xx,yy;
int v[][],h[],l[];
int dx[]={-,-,-,,,,,};
int dy[]={-,,,,,,-,-};
void apart(int d)
{
xx=ceil(d*1.0/n);
yy=d%n;
if(!yy) yy=n;
}
bool i_r(int x,int y)
{
if(h[x]!=&&l[y]!=)
return false;
return true;
}
bool i_k(int x,int y)
{
for(int i=;i<;i++)
if(v[x+dx[i]][y+dy[i]]==) return true;
return false;
}
bool i_r2(int x,int y)
{
if(h[x]==||h[x-]==||h[x+]==||l[y]==||l[y+]==||l[y-]==) return true;
return false;
}
void dfs(int d,int king,int rook,int x,int y)
{
if(rook>k) return;
if(x>n||y>n) return;
if(d==n*n+)
{
if(rook==k) ans++;
return;
}
apart(d);
dfs(d+,,,xx,yy);
bool if_rook=i_r(x,y);
bool if_king=i_k(x,y);
if(if_rook==false&&if_king==false&&k)
{
h[x]=;l[y]=;
v[x][y]=;
dfs(d+,king,rook+,xx,yy);
h[x]=;l[y]=;
v[x][y]=;
}
bool if_rook2=i_r2(x,y);
bool if_king2=i_k(x,y);
if(if_rook2==false&&if_king2==false)
{
v[x][y]=;
dfs(d+,king+,rook,xx,yy);
v[x][y]=;
}
}
int main()
{
scanf("%d%d",&n,&k);
dfs(,,,,);
printf("%d",ans);
}
上一篇:sql server 性能计数器


下一篇:Java数据类型和MySql数据类型对应表