neuq oj第11周作业题解

1146: 【C语言训练】字符串正反连接

题目描述:

所给字符串正序和反序连接,形成新串并输出

输入:

任意字符串(长度<=50)

输出:

字符串正序和反序连接所成的新字符串


简单题,我看个别同学做的太繁了,正着输出一次,再倒着输出一次就ok了,不需要引进第三个string类变量



1838: Euler theorem

题目描述:

HazelFan有两个正整数ab,他想计算a mod b。 但现在他忘了b的值,只记得a的值,请告诉他不同的可能的求余结果的数量。

输入:

第一行包含一个正整数T1≤T≤5),表示测试样例数。对于每个测试样例:一行,包含正整数a1≤a≤1e9)。

输出:

对于每个测试用例:单行包含一个非负整数,表示答案


分析:

基本方法

最简单的是枚举。枚举1e9很不现实,显然需要优化。

算法优化

算法优化1.1

先对枚举本身优化,减少枚举数量。经过数学分析我们很容易想到,5mod2和5mod4实际是一样的,9mod2,9mod4,9mod8也是一样的……别急着下结论,16mod3,16mod6,16mod12是一样的,但16mod9就不一样了。好像是这样,对于任意给定的a,任意m,得到的m*2^n(n belong to R+),余数相同。是这样吗?

算法优化1.1.1

反例很好举,比如,16mod24。于是,你还需要判断大小……枚举的优化需要复杂计算的时候,就不是很有效率了。

算法优化1.2

换个思路,哪些数一定会是余数?显然有了1.1.1中的反例,任意被除数a对b一定有余数a。而且,任意被除数a的最大余数也是a。从a向下枚举,举例a=17,余数可以是17,可以是16吗?显然不可能,同理15、14、13、12、11、10、9都不可能,而17mod9=8,17mod10=7……17mod16=1,17mod17=0。分界点在哪里呢?17的一半,8.5这里。 从0~8,加上17本身,都可以。

算法优化1.2.1

将这个集合的数量公式化。显然,0~8有九个数,是对17的一半向上取整(例:17/2+1)。

然后再+1(17本身)。得到a/2+2(a为整型变量)。

由于用到了整除操作,需要验证一下偶数,16显然为16/2+1,而不是16/2+2。

最简单的,可以分类讨论。

算法优化1.2.2

可以不分类讨论吗?当然可以。

主要以下思路:

1、 由于奇数要补1而偶数不用,给a先补上1,变成(a+1)/2+1;

2、 预处理。对奇数+1;

3、 利用a-a/2,得到向上取整的a÷2;

4、 利用向上取整函数floor(),将a/2改为a/2.0f(目的是使结果为浮点数),然后向上取整,再利用强制类型转换输出整型,或者利用流输出的控制只输出浮点数结果的整数部分。

请自行分析以上方法和分类讨论的效率。







1030: C基础-选择半径

题目描述:

编写一个程序,打印输出半径为110的圆的面积,若面积在4090之间则予以打印,否则,不予打印。


太简单了。。提两点吧:1.圆形面积   2.fixed<<setprecision(2) 







1096.NEUQ的邮票

题目描述:

neuq最近推出了一套具有特殊意义的邮票,邮票的价值是从1分到N分。并且每张邮票的价值都不相同。

谷学长看中了这套邮票,可是一掏兜发现自己只有M分。但是他非常想买,于是他决定买总价值刚好为M分的邮票,但谷学长不希望他买的邮票断断续续,所以他还希望能够买从x分到y分连续的y-x+1张邮票。

你的任务是求出所有符合要求的方案,以[x,y]的形式输出。

输入:

输入包含多组数据,每组数据只有一行,包含两个数N和M(1<=N,M<=10^9)。

输出:

对于每组输入数据,每行输出包含一个合法方案:[x,y]。按x值从小到大输出。

每组输出数据不含任何空格。


这道题乍一看上去是一道很简单的等差数列求和的问题,但由于极为庞大的数据量,和1s的判题时间,让处理问题变得比较棘手。由于判题机一秒钟大约可以处理10^8多,不到10^9的数据量(看评测机的水平了),这道题的复杂度我们必须控制在O(n)以下

首相我想到了,公式法,复杂度O(1),但这道题并不是一个输入一个输出的这种类型。。不可行。

第二:使用一次遍历,利用判断条件控制减少需要遍历的数据量,降低时间复杂度。

首先,我们需要先找到大致的O(n)的算法,分析一下已知的条件:已知等差数列的Sum(输入),等差数列的公差d=1,要求所有满足从x到y的和为Sum的方案。最普通的算法需要两个变量,首相和末相(或者首相和相数)才能求出这一段等差数列的Sum,如果这样的话我们就需要两个循环来遍历,复杂度达到了O(n^2),肯定超时。

如果我们只知道一个变量呢?

这时,需要用到求和公式了Sum=a1^n+n*(n-1)/2,用一重for循环遍历一个未知量,通过公式变形,求得另一个变量的表达式,判断该变量是否符合要求,即该变量是否为正整数,是否超过最大范围。如果符合要求,那么这组数据就是复合要求的,再通过转换求出末相输出即可。

这里初看是有两条路可以走的:

1.求出a1的表达式:a1=Sum/n+(n-1)/2,for循环遍历相数n,判断a1是否为正整数且a1<=N

2.求出相数n的表达式 :解一个关于n的二元一次方程,解得n=(1-2*a1+sqrt(4*a1*a1-4*a1+1+8*Sum))/2;判断n是否为正整数

由于2式关于n的表达式过于恶心,我选择第一种方法= =

好,我们终于将复杂度控制在O(n)了,接下来才是本题的重点:剪枝。    //找本题的隐藏条件。

这时用到了另一个求和公式:根据题目,Sum=(y-x+1)(y+x)/2,这里的(y-x+1)就代表相数,我们需要找的是关于相数的限制条件啊,得铆劲往相数上面凑,Sum是已知量,这里就得找(y-x+1)和(y+x)的关系了,而首相x>=1的,这就找到了(y-x+1)和(y+x)的不等关系:(y-x+1)<=(y+x),进而得到了相数的关键限制条件:(y-x+1)<=sqrt(2*sum)

ok,算法确定了,可以代码实现了     核心代码如下图:

ps:由于输入没有结束的标志,建议使用~scanf或者scanf()!=EOF

pps:求表达式判断是否为正整数这个方法经常用于减少时间复杂度

int m,sum;
while(~scanf("%d%d",&m,&sum))//邮票最大面额和钱的大小关系是不定的
{
    for(int i=(int)(sqrt(2*sum)+0.5);i>=1;i--)//i代表项数,倒序循环是为了保证x从小到大输出
    {
        if((2*sum-(i-1)*i)%(2*i)==0 &&(2*sum-(i-1)*i)>0)//如果a1为整数,正数
        {
            //cout<<i<<endl;
            if((2*sum-(i-1)*i)/(2*i)+i-1<=m && (2*sum-(i-1)*i)/(2*i)>0)//末相=末相<=邮票面额的最大值
            {
                cout<<'['<<(2*sum-(i-1)*i)/(2*i)<<','<<(2*sum-(i-1)*i)/(2*i)+i-1<<']'<<endl;
            }
        }
    }
}




1133: C语言考试练习题_排列


题目描述:

有4个互不相同的数字,输出由其中三个不重复数字组成的排列。 

输入:

4个整数。

输出:

所有排列。


首先,向所有暴力输出一次过的选手献上最高的敬意!

1.先从四个数字内选三个数字,有三种选法

       三种选法设置x,y,z进行三次赋值

2.每种选法有6种排列方式

       6种排列先后顺序用x,y,z先后赋值

写一个三变量的函数,在主函数里调用6次,注意顺序。



1219: 哥德巴赫曾猜测

题目描述:

德国数学家哥德巴赫曾猜测:任何大于6的偶数都可以分解成两个素数(素数对)的和。但有些偶数可以分解成多种素数对的和,如: 10=3+7,10=5+5,即10可以分解成两种不同的素数对

输入:

输入任意的>6的正偶数(<32767)

输出:

试求给出的偶数可以分解成多少种不同的素数对(注: A+B与B+A认为是相同素数


素数问题,这题很巧= = 第四周的题解也是我写的,介绍过这两种算法
方法一:
自定义一个判断是否为质数的函数,然后从令j为从2到n/2,遍历一遍,判断j和n-j是否都为质数,若是则计数器++
核心代码如下:

bool prime(int x)
{
    for(int i=2;i<=sqrt(x+0.5);i++)
        if(x%i==0)
        return false;
    return true;
}

    int n;
    scanf("%d",&n);
    int cnt=0;
    for(int j=2;j<=n/2;j++)
    {
        if(prime(j)&&prime(n-j))
            cnt++;
    }
    printf("%d",cnt);



方法二:(选看)
先找出32767内的所有质数,将其存入vector中,然后再令j为从2到n/2,判断j和n-j是否都在vector中,如果都在则计数器++。
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cstring>
#define Shift 5
using namespace std;


const int N=32800;
const int M=N>>Shift;
unsigned int a[M+1];
vector<int> prime;
vector<int>::iterator it;
vector<int>::iterator tt;
void setbit(int x)
{
     a[x>>Shift]|=1<<(x&((1<<Shift)-1));
}
bool getbit(int x)
{
    return a[x>>Shift]&(1<<(x&((1<<Shift)-1)));
}
void init()
{
    memset(a,true,sizeof(a));
    for(int i=2;i<=N;i++)
    {
        if(!getbit(i))
        prime.push_back(i);
        for(int j=0;j<prime.size()&&i*prime[j]<=N;j++)
        {
            setbit(i*prime[j]);
            if(!(i%prime[j]))
                break;
        }
    }
}


int main()
{
    init();
    int n;
    scanf("%d",&n);
    int cnt=0;
    for(int j=2;j<n/2+1;j++)
    {
        it=find(prime.begin(),prime.end(),j);
        tt=find(prime.begin(),prime.end(),n-j);
        if(it!=prime.end()&&tt!=prime.end())
            cnt++;
    }
    printf("%d",cnt);


    return 0;
}






1238: 换位置

题目描述:

M个人围成一圈,每分钟相邻的两个人可以交换位置(只能有一对交换)。求使M个人的顺序颠倒(即每个人左边相邻的人换到右边,右边相邻的人换到左边)所需的最少时间(分钟数)。

输入:

第一行为测试数据的组数n,以后n行中每行为一个小于32767的正整数,表示M

输出:

对于每组测试数据,输出一个数,表示最少需要的分钟数。


简单的找规律问题
当m为偶数时,可以将一个圈分成左右两半分别计算,两边移动时间是相同的,只需将把一半全部倒置所需的时间乘2即可,最后推出(m/2)*(m/2-1);
当m为奇数时,同理,也容易推出(m-1)/2*(m-1)/2。
核心代码如下:
    

int n,m;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&m);
        if(m%2==0)
            printf("%d\n",(m/2)*(m/2-1));  //m为偶数时;
        else
            printf("%d\n",(m-1)/2*(m-1)/2);  //m为奇数时;
    }



1249: 日期排序

题目描述:

有一些日期,日期格式为“MM/DD/YYYY”。编程将其按日期大小排列。


简单题,/*忽视bug= =*/     硬写也能写,最简洁的方法是sort函数配自定义cmp函数的结构体排序,自定义一个date结构体,再定义一个bool类型的cmp函数,在年相同的时候返回月小的,年月相等的时候返回日小的
核心代码如下:
bool compare(date a,date b)//用sort函数必须建一个compare.  
{  
    if(a.year==b.year)  
    {  
        if(a.month==b.month)  
        {  
            return a.day<b.day;  
        }  
        else  
        {  
            return a.month<b.month;  
        }  
    }  
    else  
    {  
        return a.year<b.year;  
    }  
} 
 sort(a,a+cnt,compare);




1161: 【C语言训练】百钱百鸡问题


中国古代数学家张丘建在他的《算经》中提出了著名的“百钱买百鸡问题”:鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何?


简单题,只数相加=100,钱数相加=100,两个循环遍历两种鸡的只数,内层循环里出剩下的那只的表达式(有点像neuq邮票那题),带入钱数的表达式,如果满足就按照格式输出。//样例不用管= 





1288: 青年歌手大奖赛_评委会打分

题目描述:

青年歌手大奖赛中,评委会给参赛选手打分。选手得分规则为去掉一个最高分和一个最低分,然后计算平均得分,请编程输出某选手的得分。

输入:

输入数据有多组,每组占一行,每行的第一个数是n(2n100),表示评委的人数,然后是n个评委的打分。

输出:

对于每组输入数据,输出选手的得分,结果保留2位小数,每组输出占一行。


分析:

基本方法

定义一个100以上的数组,初始化为0,将原始成绩存入数组,然后将数组遍历,将最大最小数变为0,求数组和,除以(n-2)。

算法优化

上述方法实际上遍历了两次数组。如果遍历时同时:

1、 记录已经出现的最大值;

2、 记录已经出现的最小值;

3、 记录已经出现数的和。

最后将和减去最大最小值,除(n-2)。

那么只需用一个for循环。当然这对时间的减少有限而且有条件(n较小时可能会更费时),但是如果题目允许n非常大,而且给定运行内存很小的情况下,这种方法根本不需要开数组(读一个数处理一个),能减少内存占用。







撒花完结!!!
祝各位希家家期末考试考出好成绩!!!

By  
     曹梦柯  李玲  郑涵伊  王子灿  王海阳  孙意


猜你喜欢

转载自blog.csdn.net/neuq_zsmj/article/details/78934354