JSU 2013 Summer Individual Ranking Contest - 5
JSU 2013 Summer Individual Ranking Contest - 5
密码:本套题选题权归JSU所有,需要密码请联系(http://blog.csdn.net/yew1eb)。
一、套题分析:该套题难度不大,适合入门有一定基础的ACMer用于练习。
二、考点:逻辑思维、基础数论、数位运算基础、深度优先搜索、动态规划。
三、竞赛形式:个人赛
四、解题时间:3小时
五、题目情况:
A. BNU ACM校队时间安排表
B. 硬币水题II
C. 沙漠之旅
D. Invading system
E. Sereja and Bottles
六、解题报告:
A. BNU ACM校队时间安排表
ICPC,全称国际大学生程序设计竞赛,由美国计算机协会(ACM)主办。它是一项非常公平的比赛,广受世界各地大学生的喜爱。ACM每年在各大洲会举办区域赛,表现优异的学校将有资格参加世界总决赛。
ACM-ICPC自1996年踏上中国大陆,只在上海大学设立赛点。从2008年开始,每年在中国大陆有五个赛区举办区域赛。BNU也从2002年开始组建了ACM校队,通过老师和同学的不懈努力,BNU的ACM成绩也在逐年进步。
每年BNU校队都会举办一系列的培训以及比赛,吸引优秀的同学参赛(易大神牛为此还搭建了我们学校的神OJ)。校队各项活动的时间大致安排如下:
时间 |
活动 |
英文名称 |
11月-12月 |
ACM基础培训(面向全校本科生) |
Basic Training |
12月 |
新生赛(面向全校本科一年级的同学) |
Rookie Contest |
2月-4月 |
春季培训(面向全校学生) |
Spring Training |
4月 |
校赛(面向全校本科生和硕士研究生) |
BNU Contest |
7月 |
实践周(面向全校本科生) |
Practice Week |
7月-8月 |
暑期训练(面向校队) |
Summer Training |
9月-11月 |
区域赛(校队) |
Regional Contest |
小胖是BNU ACM校队的脑残粉,他掌握了校队各项活动的时间,同学们对校队的活动安排有什么疑问都会找他。比如昨天大钰儿问他,校队5月份有什么安排,今天浪哥问他暑假7月份要做什么,等等等等。随着想加入校队的人数越来越多,询问也越来越多,小胖就写了个程序,自动回答这些询问。
Input
输入第一行有一个整数T(1<=T<=100),表示询问的个数。
接下来有T行,每一行一个整数M(1<=M<=12),代表询问的月份M。
Output
对于每一个询问,输出若干行,表示该月的活动,每一行代表一个活动的英文名称。如果某个月有多项活动,则按照上表给出的顺序输出。如果某个月没有活动,则输出一行Unknown。
Sample Input
3 1 11 7
Sample Output
Unknown Basic Training Regional Contest Practice Week Summer Training
解题思路:本题为签到水题,只要用结构体按活动表顺序存储活动相关信息。题目给出月份时(标记变量为0),按结构体下标顺序遍历,遍历到月份相同时,便输出其活动情况(标记变量赋为1)。若是结构体信息全遍历完,标记变量任然为哦,则输出“Unknown”。
#include<stdio.h> #include<string.h> struct node { int x; char num[25]; } list[15]; int main() { int n,x,i,j; list[0].x=11; //按照活动信息表存储活动信息 strcpy(list[0].num,"Basic Training"); list[1].x=12; strcpy(list[1].num,"Basic Training"); list[2].x=12; strcpy(list[2].num,"Rookie Contest"); list[3].x=2; strcpy(list[3].num,"Spring Training"); list[4].x=3; strcpy(list[4].num,"Spring Training"); list[5].x=4; strcpy(list[5].num,"Spring Training"); list[6].x=4; strcpy(list[6].num,"BNU Contest"); list[7].x=7; strcpy(list[7].num,"Practice Week"); list[8].x=7; strcpy(list[8].num,"Summer Training"); list[9].x=8; strcpy(list[9].num,"Summer Training"); list[10].x=9; strcpy(list[10].num,"Regional Contest"); list[11].x=10; strcpy(list[11].num,"Regional Contest"); list[12].x=11; strcpy(list[12].num,"Regional Contest"); while(scanf("%d",&n)!=EOF) { for(i=0; i<n; i++) { int flag=0; //标记变量初值置为0 scanf("%d",&x); for(j=0; j<13; j++) { if(list[j].x==x) //若当前结构体月份和输入月份相同,则输出活动情况 { flag=1; //标记变量置为1 printf("%s\n",list[j].num); } } if(!flag) //若标记变量为0,则输出标记变量 printf("Unknown\n"); } } return 0; }
B. 硬币水题II
小胖有一个正反面不对称的硬币。如果抛一次这个硬币,它的正面朝上的概率为p,反面朝上的概率为1-p。现在,小胖想用这个硬币来产生等概率的决策(50%对50%)。当然,只抛一次是不行的。小胖的策略是这样的:每一次决策,需要抛硬币两次,如果都是正面朝上或者都是反面朝上,那么就重新再做一次决策;如果是一正一反,那么如果第一次是正面朝上,就说抛了正面,如果第一次是反面朝上,那么就视为抛了反面。这样,就能得到一个公平的决策了。
现在问题是,给定一个p,小胖平均要抛多少次,才能得到一个决策呢(即不用再抛了)?
Input
第一行包含一个整数N(N<=100),表示测试数据的个数。
接下来包括N行,每行一个测试数据,包括一个3位的浮点数p(0<p<1)。
Output
对每一个测试数据,输出一行,包括一个浮点数,表示小胖抛硬币的平均次数。
结果保留两位小数。
Sample Input
3
0.500
0.800
0.300
Sample Output
4.00
6.25
4.76
解题思路:本题为数学题,推出数学公式,本题即可得解。
抛硬币的次数和成功的概率公式如下:
抛硬币成功的次数/抛硬币的总次数= 抛硬币成功的概率
公式化简后为:
1/抛硬币成功的平均次数=抛硬币成功的概率
公式变形后为:
1/抛硬币成功的概率=抛硬币成功的平均次数
又,抛硬币成功的平均次数=1/(p*(1-p))
测试数据给出p时,直接带入公式计算后输出结果即可。
#include<stdio.h> int main() { int n; double p; scanf("%d",&n); while(n--) { scanf("%lf",&p); p=p*(1-p); p=(double)1/p; //注意精度 printf("%.2lf\n",p); } return 0; }
C. 沙漠之旅
“小胖要穿越一片沙漠,小胖开着一辆大吉普,小胖的吉普油耗高,吉普能放四桶油。”
这就是人人会唱的沙漠之歌~~体现了小胖拔群的聪明才智。
小胖的问题是这样的:现在需要驾车穿越一片沙漠,总的行驶路程为L。小胖的吉普装满油能行驶X距离,同时其后备箱最多能放下四桶油。在起点有N种汽油,每种汽油都有无限桶,一桶能行驶距离Ai。现在小胖想知道:能不能恰好带四桶油,再加上出发前装满的油,使得恰好能行驶L距离。
Input
第一行一个正整数T(1 <= T <= 50),表示数据的组数。
接下来T组数据,每组数据的第一行是三个整数L(1 <= L <= 1000),X(1 <= X <= L),N(1 <= N <= 1000)。
接下来N行,每行一个正整数Ai(1 <= Ai <= 1000),表示第i种汽油一桶能行驶的距离。
Output
对于每组数据输出一行,若能输出“Yes”,否则输出“No”
Sample Input
1 20 9 2 2 3
Sample Output
Yes
解题思路:本题为综合性题目,解法不一,可以用多重剪枝的直接暴力搜索过,可以用深度优先搜索算法过,还可以用动态规划过,只是,不同的解发效率不同。下面给出深度优先搜索算法的AC代码、深度优先搜索算法剪枝后的AC代码和动态规划算法的AC代码(共4个)。
(1)深度优先搜索基础算法AC代码:
直接用深度优先算法解答,对每种汽油的情况搜索4次,以现已计汽油桶数和可行公里数为深搜参数,标记变量初始值置为0。当汽油桶数大于4或总行驶路程数大于l-x时,后面的搜索再不可能满足要求,借宿搜索。当汽油桶数为4并且行驶路程为l-x时符合题意要求,标记变量置为1,结束搜索。搜索结束后,若标记变量为1,则满足题意“恰好带四桶油,再加上出发前装满的油,使得恰好能行驶L距离。”输出“Yes”,否则输出“No”。
//20124045007 C Accepted GNU C++ 220 ms 1312 KB [ 629 B ] 2013-08-15 15:03:55 #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int l,x,n,ans; int a[1100]; //存储1桶汽油行驶的路程情况 void dfs(int cur,int sum ) //使用cur种汽油,可行驶sum公里 { int i,j; if(sum>l-x || cur>4) return; //行驶路程超出,或桶数大于4,后面搜索不可能满足题目要求,中断该分支的搜索 if(ans) return; //已经搜索成功,不用继续搜索 if(cur==4 && sum==l-x) //搜索成功,改变标记变量的值,结束搜索 { ans=1; return; } for(i=1; i<=n; i++) //对于每种汽油的情况进行搜索 { for(j=1; j<=4; j++) //每种汽油最多带4桶 dfs(cur+j,sum+j*a[i]); } } int main() { int i,t; scanf("%d",&t); while(t--) { scanf("%d%d%d",&l,&x,&n); for(i=1; i<=n; i++) scanf("%d",&a[i]); sort(a+1,a+1+n); // for(i=1;i<=n;i++) printf("%d ",a[i]); ans=0; dfs(0,0); //开始搜索,带了0桶汽油,能行驶0公里 if(ans) printf("Yes\n"); //搜索成功,有满足题意的组合 else printf("No\n"); } return 0; }
(2)深度优先搜索改进算法AC代码:(效率比基础AC代码提高1倍多,内存稍有增加)
基本集体思路同深度优先搜索基础AC代码,只是深度优先搜索函数中增加参数i,记录上层搜索搜索到第i-1种汽油,对于已经搜索过的情况,不必再搜索,现搜索从第i总汽油开始。成功剪枝,减少程序耗时,只是参数增加i,程序内存稍有增加。
//20114045007 252553 29376 Accepted GNU C++ 108 ms 1316 KB [ 780 B ] 2013-08-16 09:25:47
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int l,x,n,ans;
int a[1100];
void dfs(int i,int cur,int sum ) //从本层搜索从第i桶开始,前面的搜索,带cur桶汽油,能行驶sum公里
{
int j;
if(sum>l-x || cur>4) return;
if(ans) return;
if(cur==4 && sum==l-x)
{
ans=1;
return;
}
for(; i<=n; i++)
{
for(j=1; j<=4; j++)
dfs(i+1, cur+j,sum+j*a[i]);
}
}
int main()
{
int i,t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&l,&x,&n);
for(i=1; i<=n; i++)
scanf("%d",&a[i]);
sort(a+1,a+1+n);
// for(i=1;i<=n;i++) printf("%d ",a[i]);
ans=0;
dfs(1,0,0);
if(ans) printf("Yes\n");
else printf("No\n");
}
return 0;
}
(3)深度优先搜索改进算法AC代码2:(效率比基础AC代码提高将近7倍,内存不变)
基本集体思路基本同深度优先搜索改进AC代码,只是搜索范围减少,对汽油的行驶路程情况进行从小到大排序,对于一桶汽油行驶的路程就大于等于l-x的,直接省略,不予处理。同时,若是l-x不能被平分为4份的,对于每种汽油搜索次数减为3。有效剪枝,减少搜索范围和搜索次数。
//20114045007 252568 29376 Accepted GNU C++ 36 ms 1312 KB [ 933 B ] 2013-08-16 09:54:06 #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int a[1002]; int l,x,n,m; int flag; void dfs(int i,int count,int sum) { int j; if(count>4||sum>l||flag) return; if(count==4&&sum==l) { flag=1; return ; } for(;i<n;i++) for(j=1;j<=m;j++) dfs(i+1,count+j,sum+j*a[i]); } int main() { int t; int i,j; scanf("%d",&t); while(t--) { scanf("%d%d%d",&l,&x,&n); l=l-x; for(i=0;i<n;i++) scanf("%d",&a[i]); sort(a,a+n); for(i=0;i<n;i++) if(a[i]>=l) //若第a[i]种汽油,一桶的行驶路程不小于l-x,后面的搜索情况不能满足题意,直接舍弃后面的数据 { n=i; break; } m=4; if(l%4!=0) //若l-x不能被4整除,每种汽油最多带3桶,可能满足题意 m=3; flag=0; dfs(0,0,0); if(flag) printf("Yes\n"); else printf("No\n"); } return 0; }
(4)动态规划算法AC代码:
动态转移方程:dp[i+a[k]][j+1]=true;(dp[i][j]初始化为false)
语义为:共带j+1桶汽油,可以恰好行驶i+a[k]公里,其前提条件是:a[i][j]=true(带j桶汽油,能恰好行驶j公里)。
其实就是,在前k种汽油中,带j桶恰好可以行驶i公里,现在再加带k种汽油1桶,则可以恰好行驶i+a[k]公里。
//20114045007 252576 29376 Accepted GNU C++ 44 ms 1312 KB [ 710 B ] 2013-08-16 10:21:22 #include<stdio.h> #include<string.h> using namespace std; int main() { int t; int l,x,n; int a[1002]; bool dp[1010][6]; int i,j,k; scanf("%d",&t); while(t--) { scanf("%d%d%d",&l,&x,&n); l=l-x; for(i=0;i<n;i++) scanf("%d",&a[i]); memset(dp,false,sizeof(dp)); dp[0][0]=true; for(i=0;i<l;i++) for(j=0;j<4;j++) if(dp[i][j]) for(k=0;k<n;k++) if(i+a[k]<=l) dp[i+a[k]][j+1]=true; if(dp[l][4]) printf("Yes\n"); else printf("No\n"); } return 0; }
D. Invading system
Input
Input contains multiple test cases.
For each test case, the first line has a number n (1<=n<=10^5), showing the number of the numbers. The second line contains n integers separated by blank, showing the n numbers in the file (1<=ai<=10^9).
Output
Sample Input
5
3 2 4 5 6
Sample Output
解题思路:本题考查数位运算,用右移即可解得。
给一个数t,表示有t组测试数据。你一个数n,表示该组测试数据有n个数。后面给出n个数,用空格隔开。要求找出给出的n个数中,二进制中1的个数最少的数中数值最少的那个数。
定义变量m,sum,分别存储当前数的值和当前数的二进制的中1的个数;定义变量min1,least,初始值为极大值,分别存储,当前所找到的数中,二进制中1的个数最少的数中数值最少的那个数的数值和1的个数。
对于每个给定的数x,用m转储其值,再对其处理。对其循序处理,直到x的值为0.每次循环处理,先判断x%2!=0?若为true,则表示x处理至先下的值中的二进制的最低位为1,sum++,并且x>>1(x右移1位)[用于测定x的二进制中1的个数]。若当前x的x的二进制中1的个数小于least或等于least并且当前m值小于min1,则用之替换。
#include<stdio.h> int main() { int t,n; int x,m,sum; int min1,least; int i,j; scanf("%d",&t); for(i=1; i<=t; i++) { min1=0xfffffff; //初始化为极大值 least=0xfffffff; scanf("%d",&n); while(n--) { sum=0; scanf("%d",&x); m=x; if(x==min1) //若当前数与找出来的数相等,则不必处理 continue; while(x) //x不为0,测算x的二进制中1的个数 { if(x%2!=0) //若x的二进制的最低位为1,sum++ sum++; x=x>>1; //x右移1位 } if(sum<least||sum==least&&m<min1) //当前x的二进制中1的个数为所有处理过得数中值最少 //或当前x的二进制中1的个数等于所有处理过得数中值且m的值最小 { least=sum; min1=m; } } printf("Case %d: %d\n",i,min1); } }
E. Sereja and Bottles
Sereja knows that the i-th bottle is from brandai, besides, you can use it to openother bottles of brand bi. You can use one bottle to open multiple other bottles. Sereja can open bottle with opened bottle or closed bottle.
Knowing this, Sereja wants to find out the number of bottles they've got that they won't be able to open in any way. Help him and find this number.
Input
The first line contains integer n (1 ≤ n ≤ 100) — the number of bottles. The next n lines contain the bottles' description. The i-th line contains two integers ai, bi(1 ≤ ai, bi ≤ 1000) — the description of thei-th bottle.
Output
In a single line print a single integer — the answer to the problem.
Sample Input
4 1 1 2 2 3 3 4 4
4
4 1 2 2 3 3 4 4 1
0
解题思路:本题为逻辑题,只要搞清楚题目中逻辑意思容易解得。
给一个n,表示瓶子的个数,接下来n组数,每组数由a,b组成表示有一瓶子为品牌a,可打开品牌b的瓶子。要求出所有瓶子中不能打开的瓶子的个数。
对于每组给定的瓶子,用哈希表存储其b的情况,每次遇到b,则存储为a[b]++,表示有a[b]个瓶子可以打开品牌b的瓶子。然后检索所有的瓶子信息,若a[a]为0,表示没有能打开a品牌的瓶子的瓶子,即该瓶子无法打开,计数变量加1(对每组测试数据,初始化为0),或a==b(品牌a的瓶子只能由本品牌的瓶子来打开)并且a[a]为1,所有瓶子中只有1个a品牌的瓶子(即它本身),则该瓶子无法打开,计数变量加1。最后输出计数变量的值即可。
#include<stdio.h> #include<string.h> struct node { int a; int b; } bottle[102]; int main() { int a[1001]; int x,y; int n; int i,j; int sum; scanf("%d",&n); sum=0; memset(a,0,sizeof(a)); for(i=0; i<n; i++) { scanf("%d%d",&bottle[i].a,&bottle[i].b); a[bottle[i].b]++; //能打开bottle[i].b品牌的瓶子有a[bottle[i].b]个 } for(i=0; i<n; i++) { if(!a[bottle[i].a]||bottle[i].a==bottle[i].b&&a[bottle[i].a]==1) //该瓶子无法打开 //没有瓶子可以打开瓶子||只有该瓶子自身能打开自身 sum++; } printf("%d\n",sum); return 0; }