大题
后缀式转中缀式
【问题描述】
将由数字和四则运算符组成的后缀表达式变换为中缀表达式。输入的后缀表达式包含的运算符不超过15个。要求转换后的中缀表达式中不应出现不必要的括号。例如,整个表达式两端的括号要省略,不影响原计算结果的括号要省略。
【输入形式】
程序从标准输入上读入一行字符串,是一个合法的后缀表达式,数字和运算符之间由空格分隔。其中的数字可以是整数,也可以是带有小数部分的浮点数。
【输出形式】
向标准输出打印结果。 输出只有一行,是转换后的中缀表达式,并且 1. 各分量(包括括号)紧密输出,不使用空格进行分隔; 2. 在转换前后各运算数的出现顺序不变; 3. 浮点数保留输入时的小数位数。
【输入样例】
4 7 - 2.1 5 + * 7.1 9 - /
【输出样例】
(4-7)*(2.1+5)/(7.1-9)
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为convert.c。
思路:感觉这是比较难的一道题,难在后缀转中缀的加括号问题上。首先,我们要根据一个后缀表达式建立一棵树,然后想变成中缀表达式只需中序遍历这棵树即可。但问题就在于遍历输出表达式的过程中还要判断什么时候输出括号。
判断是否加括号的条件:
1. 无论左右子树,只要子树操作符的优先级小于父节点运算符优先级,则子树要加括号;
2. 仅对左子树,如果左子树的优先级等于父节点运算符优先级,则肯定先算子树,所以无需加括号;
3. 仅对右子树,如果右子树的优先级等于父节点运算符优先级,则如果父节点的运算符是’-‘或’/’,右子树需要加括号;
4. 无论左右子树,只要子树的优先级大于副节点的运算符的优先级,无需加括号。
所以实际加括号的情况有1和3两种。
通过上面的分析我们可以看出,在遍历子树的时候,我们需要知道子树的根存储的运算符的优先级和其父节点的运算符的优先级的大小关系,然后才能判断是否加括号,所以在遍历子树时如何获取这个关系成为了一个难点。
或许可以考虑将普通的树转变为带有子节点指向父节点指针的线索二叉树(貌似是这个叫法),这样的话获取这个关系会变得容易(有空可以试一下),不过构造树的过程会变得稍微有些复杂。
在依然使用普通的二叉树的情况下,下面的代码使用了void mid_traverse(Node *root)
和void mid_traverse_with_bracket(Node *root, int flag)
两个函数才实现中序遍历并加括号这个功能(其实就是比普通的中序遍历多一步:每次进入子节点前判断一下父节点和子节点的优先级,考虑要不要加括号,然后再进入子节点开始递归。因此也完全可以将两个函数合并成一个函数,或许会更清晰),是比较好的实现了。
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#define IS_NUM(x) ((x) >= '0' && (x) <= '9') //判断x是否为整数,使用宏定义比较简洁
#define LENGTH 1024
#define N 50
char s[LENGTH], buffer[N][LENGTH]; //s存放原始字符串;buffer为二维数组,分别存储原始字符串切分出来的token
int size;
typedef struct node{ //建立数的节点
char token[LENGTH]; //token:数字或运算符
struct node *lchild, *rchild;
}Node;
Node* new_node(char *a, Node *l, Node *r) //新建一个节点
{
Node *root = (Node *)malloc(sizeof(Node));
memset(root, '0', sizeof(Node)); //将malloc申请的空间初始化
strcpy(root -> token, a);
root -> lchild = l;
root -> rchild = r;
return root;
}
//free the node:程序的最后不要忘了free掉malloc申请的空间
//我懒,就不写free了=.=
Node* build() //建立一棵树
{
int p = --size; //由于输入是后缀表达式,所以要倒着构建树,即最后一个字符其实是树的根节点
if(IS_NUM(buffer[size][0])){ //数字是叶结点
return new_node(buffer[p], NULL, NULL);
}
else{ //运算符是非叶结点
return new_node(buffer[p], build(), build());
}
}
int level(char *a) //为不同的token赋予不同的优先级
{
switch(a[0]){
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
void mid_traverse(Node *root); //中序遍历一棵树
void mid_traverse_with_bracket(Node *root, int flag) //后缀转中缀,所以中序遍历的同时判断是否加括号
{
if(flag){
putchar('(');
}
mid_traverse(root);
if(flag){
putchar(')');
}
}
void mid_traverse(Node *root)
{
if(root -> lchild == NULL && root -> rchild == NULL){ //如果是叶结点,直接输出
printf("%s", root->token);
}else{ //如果不是叶结点(即:是运算符),则对是否加括号进行判断
/*判断是否加括号的条件:
* 1.无论左右子树,只要子树操作符的优先级小于父节点运算符优先级,则子树要加括号;
* 2.仅对左子树,如果左子树的优先级等于父节点运算符优先级,则肯定先算子树,所以无需加括号;
* 3.仅对右子树,如果右子树的优先级等于父节点运算符优先级,则如果父节点的运算符是'-'或'/',右子树需要加括号;
* 4.无论左右子树,只要子树的优先级大于副节点的运算符的优先级,无需加括号。
*
* 所以加括号的情况有1和3两种。
*/
//判断左子树
mid_traverse_with_bracket(root -> lchild, level(root->token) == 2 && level(root->lchild->token) == 1); //情况1
printf("%s", root->token);
//判断右子树
mid_traverse_with_bracket(root -> rchild,
level(root->token) != 0 && level(root->rchild->token) != 0 &&
(level(root->token) > level(root->rchild->token) //情况1
||
(level(root->token) == level(root->rchild->token) //情况3
&& (root->token[0] == '-' || root->token[0] == '/'))) );
}
}
void read() //读取字符串,并使用sscanf切分进二维数组
{
int offset; //相对于s其实位置的偏移量
char *p = s;
gets(s); //读取原始字符串
while(sscanf(p, "%s%n", buffer[size], &offset) != EOF && buffer[size][0] != '\n'){
size++;
p += offset;
}
}
int main()
{
Node *root = NULL;
read();
root = build();
mid_traverse(root);
putchar('\n');
return 0;
}
N的阶乘
【问题描述】
精确计算N的阶乘。其中,N可能是小于200的任意正整数。
【输入形式】
输入文件为当前目录下的factor.in。 该文件只包含一个正整数,表示需要求该正整数的阶乘。
【输出形式】
输出文件为当前目录下的factor.out。 该文件只包含一个正整数,表示该正整数的阶乘,每行最多输出50个数字。
【输入样例】
57
【输出样例】
40526919504877216755680601905432322134980384796226
602145184481280000000000000
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为factor.c。
思路:高精乘,高精度乘以常数。
#include<stdio.h>
#define N 1000
#define MAXLENGTH 50
int a[N]; //用int型数组来表示!!!!!不用char!存数的时候是倒着存的,即a[0]存的是个位!
int digits, carry = 0; //digits:当前数组的长度;carry:进位
void multi(int num)
{
int i;
for(i = 0; i < digits; i++){
a[i] = a[i] * num + carry;
carry = a[i] / 10;
a[i] %= 10;
}
while(carry){ //carry会存在不只是个位的情况,因为我们是拿整个num去乘以a[i]的,而不是我们平时用num的某一位去乘以a[i]
a[i] = carry % 10;
carry = carry / 10;
i++;
}
digits = i;
}
void print()
{
int i, count = 0;
for(i = digits - 1; i >= 0; i--){
printf("%d", a[i]);
count++;
if(count == MAXLENGTH){
putchar('\n');
count = 0;
}
}
}
int main()
{
int n, i;
freopen("factor.in", "r", stdin);
freopen("factor.out", "w", stdout);
scanf("%d", &n);
a[0] = 1;
digits = 1;
for(i = 2; i <= n; i++){
multi(i);
}
print();
return 0;
}
凸多边形面积
【问题描述】
给出平面上一组顶点的坐标,计算出它们所围成的凸多边形的面积.
输入数据表示了如图所示的四边形。其面积为5.00
评分标准:
本程序允许使用数学库函数,如果你的输出与标准答案相差不超过0.02则得满分。
【输入形式】
从标准输入读取N(3≤N≤15)行,每行两个数字(由空格隔开),分别表示该点的X、Y坐标(0≤X,Y≤32767)。所有点的坐标互不相同,且按顺时针次序给出。
【输出形式】
向标准输出打印一个浮点数,是该多边形的面积。该浮点数保留两位小数。
【输入样例】
3 3
3 0
1 0
1 2
【输出样例】
5.00
【时间限制】
2s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为points.c。
思路:将凸多边形切割为许多三角形,用海伦秦九韶公式分别求面积,加起来即可。
#include<stdio.h>
#include<math.h>
#define N 16
double point[N][2];
double square(int i)
{
double a, b, c, p;
double x1, y1, x2, y2, x3, y3;
x1 = point[0][0], y1 = point[0][1];
x2 = point[i - 1][0], y2 = point[i - 1][1];
x3 = point[i][0], y3 = point[i][1];
a = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
b = sqrt((x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3));
c = sqrt((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3));
p = (a + b + c) / 2;
return sqrt(p * (p - a) * (p - b) * (p - c));
}
int main()
{
int n = 0, i;
double S = 0;
while(scanf("%lf%lf", &point[n][0], &point[n][1]) != EOF){
n++;
}
for(i = 2; i < n; i++){
S += square(i);
}
printf("%.2lf\n", S);
return 0;
}
浮点计算
【问题描述】
计算 k *∑(x^i), -m ≤ i ≤ n,精确到小数点后14位(最后要求四舍五入)。
【输入形式】
从文件sigma.in中顺序读入4个由空格分隔的正整数k、x、m、n。(1≤ k、x、m、n ≤ 100)。
【输出形式】
将结果写到标准输出,其中小数占14位,不足部分补零,最后输出一个回车。(输入数据保证输出结果小于2^53。)
【输入样例】
1 10 5 5
【输出样例】
111111.11111000000000
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为sigma.c。
思路:如果直接求肯定精度不对,但是本题又可以不需要写高精度,因为题中有一句很重要的限制:(输入数据保证输出结果小于2^53。)即,结果的整数部分是可以用long long表示的。
所以本题可以将结果保存为两部分——
x0+x^1+⋯+xn
肯定都是整数;
x−m+x−m+1+⋯+x−1
产生了本题的小数,当然,在乘以k之后有可能会大于1。(在求这一部分的时候,注意要先乘以k,再做除法,而不是在做完除以x^m后再乘以k,因为那样相当于将除法产生的误差放大了k倍!)不过由于题目要求保留到小数点后14位,所以可以使用double保存结果的小数。
所以,本题将求和式分为两部分分别计算,一部分是整数部分,用long long保存,另一部分是小数部分,用double保存,最后分别输出结果。
#include<stdio.h>
#include<string.h>
long long power(int n, int m)
{
int i;
long long integer = 1;
for(i = 0; i < m; i++){
integer *= n;
}
return integer;
}
long long cal_integer_part(int k, int x, int n) //计算整数部分
{
long long integer = 0;
integer = (power(x, n + 1) - 1) / (x - 1);
return integer * k;
}
double cal_decimal_part(int k, int x, int m) //计算小数部分
{
int i;
double decimal = 1.0 * k * (power(x, m) - 1) / (x - 1);
//要先乘以k,而不是在做完除以x^m后再乘以k,那样相当于将除法产生的误差放大了k倍!
for(i = 0; i < m; i++){
decimal *= 1.0 / x;
}
return decimal;
}
int main()
{
int i;
int k, x, m, n;
long long integer;
double decimal;
char tmp[20] = {0};
freopen("sigma.in", "r", stdin);
scanf("%d%d%d%d", &k, &x, &m, &n);
if(x == 1){ //千万注意不要divided by zero
printf("%d", k * (m + n + 1) );
putchar('.');
for(i = 0; i < 14; i++){ //输出14个0
putchar('0');
}
putchar('\n');
}
else{
integer = cal_integer_part(k, x, n);
decimal = cal_decimal_part(k, x, m);
while(decimal > 1){ //小数部分*k可能>1,如果是则将整数转移到integer
decimal -= 1;
integer += 1;
}
sprintf(tmp, "%.14lf", decimal); //输出小数到tmp
printf("%lld", integer);
puts(tmp + 1); //获取小数点及之后的小数部分
}
return 0;
}
数据的符号
【问题描述】
将N(1<= N <= 200000)个整数小到大连续编号,相同的数应具有相同的编号。并按这N个数输入时的顺序输出它们的编号序列。例如,设输入数据为 5 3 4 7 3 5 6,则输出数据为3 1 2 5 1 3 4。
【输入形式】
从标准输入读取数据。 输入包含N个数字(1<= N <= 200000),之间由空格分隔,以回车符作为结束
【输出形式】
计算结果输出到标准输出。 按这N个数输入时的顺序输出它们的编号序列。每个序号之后紧跟一个空格符,最后输出回车符。
【输入样例】
5 3 4 7 3 5 6
【输出样例】
3 1 2 5 1 3 4
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为data.c。
思路:这题思路比较简单。先标记出场顺序,之后做个排序,标记好大小序,最后再按照出场序排列回去,输出大小序即可。需要注意的就是qsort对结构体进行排序,排序函数要注意符号的优先级。
#include<stdio.h>
#include<stdlib.h>
#define N 200010
typedef struct node{
int x; //原始数据
int y; //输入次序
int z; //按原始数据大小排序后的次序
}Node;
Node a[N];
int cmp1(const void *a, const void *b) //按照原始数据大小排序
{
// return (Node *)a->x - (Node *)b->x;
return (*(Node *)a).x - (*(Node *)b).x; //切记'->'的优先级高于强制转型!!!
}
int cmp2(const void *a, const void *b)
{
return (*(Node *)a).y - (*(Node *)b).y;
}
int main()
{
int n = 0, i, j;
while(scanf("%d", &a[n].x) != EOF){ //读取原始数据
a[n].y = n; //标记数据出现顺序
n++;
}
qsort(a, n, sizeof(a[0]), cmp1); //按照原始数据大小排序
for(i = 0, j = 1; i < n; i++){ //对排序后的数据标记大小次序
if(i != 0){
if(a[i].x != a[i - 1].x){
a[i].z = ++j;
}
else{
a[i].z = j;
}
}
else{
a[i].z = j;
}
}
qsort(a, n, sizeof(a[0]), cmp2); //恢复到原始数据输入时的次序
for(i = 0; i < n - 1; i++){ //输出大小次序
printf("%d ", a[i].z);
}
printf("%d\n", a[i].z);
return 0;
}