昨天在微博上看到王道论坛说晚上有机试练习赛(http://ac.jobdu.com/contest.php?cid=1053),想来反正也没什么事,何不参加一下。于是晚上吃完饭回到实验室,等着七点开始做题。练习赛结束了,虽然自己全部AC,但感觉时间花的有点长,主要卡在第三道求公约数个数上。看来自己的数论真的弱爆了。现将自己对这四道解题的看法归纳一下,总的来说题目不算很难,前两道超级水,第三道考素数筛选以及因式分解,第四道动态规划。
一、调整方阵
对应题目1470:http://ac.jobdu.com/problem.php?pid=1470
- 题目描述:
-
输入一个N(N<=10)阶方阵,按照如下方式调整方阵:
1.将第一列中最大数所在的行与第一行对调。
2.将第二列中从第二行到第N行最大数所在的行与第二行对调。
依此类推...
N-1.将第N-1列中从第N-1行到第N行最大数所在的行与第N-1行对调。
N.输出这个方阵
- 输入:
-
包含多组测试数据,每组测试数据第一行为一个整数N,表示方阵的阶数.
接下来输入这个N阶方阵.
- 输出:
-
调整后的方阵
- 样例输入:
-
4 3 6 8 7 6 7 5 3 8 6 5 3 9 8 7 2
- 样例输出:
-
9 8 7 2 6 7 5 3 3 6 8 7 8 6 5 3
水题,就不多说了。代码
1 #include <stdio.h> 2 3 int data[10][10]; 4 5 void adjustment(int i,int n); 6 int main() 7 { 8 int N; 9 int i,j; 10 while(scanf("%d",&N) != EOF){ 11 for(i=0;i<N;++i) 12 for(j=0;j<N;++j) 13 scanf("%d",&data[i][j]); 14 for(i=0;i<N-1;++i){ 15 adjustment(i,N); 16 } 17 for(i=0;i<N;++i){ 18 printf("%d",data[i][0]); 19 for(j=1;j<N;++j) 20 printf(" %d",data[i][j]); 21 printf(" "); 22 } 23 } 24 return 0; 25 } 26 27 void adjustment(int i,int n) 28 { 29 int max_i = i; 30 int j = i+1; 31 int temp[10]; 32 for(;j<n;++j) 33 if(data[j][i] > data[max_i][i]) 34 max_i = j; 35 if(max_i != i){ 36 for(j=0;j<n;++j){ 37 temp[j] = data[i][j]; 38 data[i][j] = data[max_i][j]; 39 data[max_i][j] = temp[j]; 40 } 41 } 42 }
二、货币问题
对应题目1549:http://ac.jobdu.com/problem.php?pid=1549
- 题目描述:
-
已知有面值为1元,2元,5元,10元,20元,50元,100元的货币若干(可认为无穷多),需支付价格为x的物品,并需要恰好支付,即没有找零产生。
求,至少需要几张货币才能完成支付。
如,若支付价格为12元的物品,最少需要一张10元和一张2元,即两张货币就可完成支付。
- 输入:
-
输入包含多组测试数据,每组仅包含一个整数p(1<=p<=100000000),为需支付的物品价格。
- 输出:
-
对于每组输入数据,输出仅一个整数,代表最少需要的货币张数。
- 样例输入:
-
10 11 13
- 样例输出:
-
1 2 3
也是水题吧,有点贪心的思想吧。先尽可能用最大面值,然后依次考虑较小面值。这题如果限制货币的数量的话,应该就是0-1背包问题吧。
代码
1 #include <stdio.h> 2 3 int main() 4 { 5 int p; 6 int i; 7 int sum; 8 int money[7] = {1,2,5,10,20,50,100}; 9 while(scanf("%d",&p) != EOF){ 10 sum = 0; 11 for(i=6;i>=0;--i){ 12 sum += (p/money[i]); 13 p %= money[i]; 14 } 15 printf("%d ",sum); 16 } 17 return 0; 18 }
三、公约数
对应题目1493:http://ac.jobdu.com/problem.php?pid=1493
- 题目描述:
-
给定两个正整数a,b(1<=a,b<=100000000),计算他们公约数的个数。
如给定正整数8和16,他们的公约数有:1、2、4、8,所以输出为4。
- 输入:
-
输入包含多组测试数据,每组测试数据一行,包含两个整数a,b。
- 输出:
-
对于每组测试数据,输出为一个整数,表示a和b的公约数个数。
- 样例输入:
-
8 16 22 16
- 样例输出:
-
4 2
一直被卡在这道题的超时上。我的做法:采用素数筛选法通过函数computePrime(int n,int maxC)返回第n个素数,其中只需要判断maxC之前的数,注意为了减少空间,我把所有的偶数(当然除了2)都去掉了,所以flag数组只有总数M的一半。在主函数里,首先求出最大公约数,则公约数的个数就等于这个最大公约数的所有约数个数,然后对这个公约数进行因式分解。即假设最大公约数为maxC,则其因式分解为:
egin{equation*}maxC=p_1^{x_1} imes p_2^{x_2} imescdots imes p_m^{x_m}end{equation*}
所以其公约数个数为: $(x_1+1) imes(x_2+1) imescdots imes(x_m+1)$,这个好像叫约数个数定理吧。
代码:
1 #include <stdio.h> 2 #include <math.h> 3 4 #define M 100000000 5 bool flag[M/2]; 6 int prime[6000000]; 7 int primeNum; 8 9 int computePrime(int n,int maxC) 10 { 11 if(n == 1){ 12 prime[primeNum++] = 2; 13 return 2; 14 } 15 if(n == 2){ 16 prime[primeNum++] = 3; 17 return 3; 18 } 19 int i,j; 20 int pos; 21 i = prime[primeNum-1]+2; 22 while(primeNum < n){ 23 pos = ((i-3)>>1); 24 if(!flag[pos]){ 25 prime[primeNum++] = i; 26 j = 3*i; 27 while(j<=maxC){ 28 flag[(j-3)>>1] = 1; 29 j += (i<<1); 30 } 31 } 32 i += 2; 33 } 34 return prime[n-1]; 35 } 36 int main() 37 { 38 int a,b; 39 int sum; 40 int maxC,i,j; 41 primeNum = 0; 42 while(scanf("%d%d",&a,&b) != EOF){ 43 int t; 44 while(a%b != 0){ 45 t = a; 46 a = b; 47 b = t%b; 48 } 49 maxC = b; 50 sum = 1; 51 i = 0; 52 int prime_i; 53 while(maxC != 1){ 54 prime_i = computePrime(i+1,maxC); 55 if(maxC % prime_i) 56 ++i; 57 else{ 58 t=0; 59 while(maxC%prime_i == 0){ 60 ++t; 61 maxC /= prime_i; 62 } 63 sum *= (t+1); 64 ++i; 65 } 66 } 67 printf("%d ",sum); 68 } 69 return 0; 70 }
四、分糖果
对应题目1550:http://ac.jobdu.com/problem.php?pid=1550
- 题目描述:
-
给从左至右排好队的小朋友们分糖果,
要求:
1.每个小朋友都有一个得分,任意两个相邻的小朋友,得分较高的所得的糖果必须大于得分较低的,相等则不作要求。
2.每个小朋友至少获得一个糖果。
求,至少需要的糖果数。
- 输入:
-
输入包含多组测试数据,每组测试数据由一个整数n(1<=n<=100000)开头,接下去一行包含n个整数,代表每个小朋友的分数Si(1<=Si<=10000)。
- 输出:
-
对于每组测试数据,输出一个整数,代表至少需要的糖果数。
- 样例输入:
-
3 1 10 1 3 6 2 3 2 1 1
- 样例输出:
-
4 5 2
看完题目,第一反应是要用动态规划解决。其最优子结构为:假如我们已经解决了(s,mid)以及(mid+1,e)这两个子问题,那么我们现在要按如下方法解决子问题(s,e)。首先分三种情况:1)score[mid]=score[mid+1];2)score[mid] > score[mid+1];3)score[mid] < score[mid+1]。
1)由于分数相等的话对糖果的分配不做要求,所以最小的糖果数是两个子问题的和。
2)左边的分数大。此时若左边的糖果多的话,则最小糖果数也是两个子问题的和;否则调整左边的糖果分配,先将[mid]位置的糖果数分配为比[mid+1]位置多一个,然后从mid到s进行调整。
3)右边的分数大。此时若右边的糖果多的话,则最小糖果数也是两个子问题的和;否则调整右边的糖果分配,先将[mid+1]位置的糖果数分配为比[mid]位置多一个,然后从mid+1到e进行调整。
代码
1 #include<stdio.h> 2 3 int candyNum[100000]; 4 int score[100000]; 5 6 int distributeCandy(int,int); 7 int main() 8 { 9 int n,i; 10 while(scanf("%d",&n)!= EOF){ 11 for(i=0;i<n;++i){ 12 scanf("%d",&score[i]); 13 } 14 printf("%d ",distributeCandy(0,n-1)); 15 } 16 return 0; 17 } 18 19 int distributeCandy(int s,int e) 20 { 21 if(s == e){ 22 candyNum[s] = 1; 23 return 1; 24 }else if(s+1 == e){ 25 if(score[s] == score[e]){ 26 candyNum[s] = candyNum[e] = 1; 27 return 2; 28 }else if(score[s] > score[e]){ 29 candyNum[s] = 2; 30 candyNum[e] = 1; 31 return 3; 32 }else{ 33 candyNum[s] = 1; 34 candyNum[e] = 2; 35 return 3; 36 } 37 }else{ 38 int total = 0; 39 int mid = (s+e)/2; 40 int left = distributeCandy(s,mid); 41 int right = distributeCandy(mid+1,e); 42 if(score[mid] == score[mid+1]){ 43 return left + right; 44 }else if(score[mid] > score[mid+1]){ 45 if(candyNum[mid] > candyNum[mid+1]) 46 return left+right; 47 else{ 48 total += (candyNum[mid+1]+1-candyNum[mid]); 49 candyNum[mid] = candyNum[mid+1] + 1; 50 int i = mid - 1; 51 int totalAdd = 0; 52 for(;i>=s;--i){ 53 if(score[i] <= score[i+1] || (score[i] > score[i+1] && candyNum[i] > candyNum[i+1])) 54 break; 55 else{ 56 total += (candyNum[i+1]+1-candyNum[i]); 57 candyNum[i] = candyNum[i+1] +1; 58 } 59 } 60 return left+right+total; 61 } 62 }else{ 63 if(candyNum[mid] < candyNum[mid+1]) 64 return left+right; 65 else{ 66 total += (candyNum[mid]+1-candyNum[mid+1]); 67 candyNum[mid+1] = candyNum[mid]+1; 68 int i = mid+2; 69 for(;i<=e;++i){ 70 if(score[i] <= score[i-1] || (score[i] > score[i-1] && candyNum[i] > candyNum[i-1])) 71 break; 72 else{ 73 total += (candyNum[i-1]+1-candyNum[i]); 74 candyNum[i] = candyNum[i-1]+1; 75 } 76 } 77 return left+right+total; 78 } 79 } 80 } 81 }
PS:此次第一名竟然在十四分钟内四道题全部AC,真是不可思议,估计有些题之前做过,直接拷贝过去或者稍微改一下。