杭电OJ/洛谷OJ 数学类题目解法及思路

Posted on 2020-09-15 21:05  ZOMIN28  阅读(532)  评论(0编辑  收藏  举报

一、前言

笔者将按照题目的类型进行博客的发表,仅供学习、交流和参考。

二、题解

1002、A + B Problem II


此题依据要求,是很常见的使用字符串解决加法问题,这里要对字符的运算有一定认识,即两个字符的加减运算即为其ASCII码的加减运算,其中字符‘0’的ASCII码为48,以此类推。

思路:将两个需要进行加法运算的数A,B,分别用字符串s1,s2保存,再对s1,s2从低位到高位依次进行加法运算,同时设置标志位flag,表示是否进位。在此题中有一个问题就是,将依次加法运算得到的数放到存储结构中后,在输出时需要逆向输出,才是真正的运算结果,这里可以使用字符串来保存结果,但考虑到“先进后出”的特征,我们使用栈结构stack来存储依次得到的数据。

关于栈stack:

包括在stack头文件中,使用时需要#include<stack>

功能如下:

(1)构造函数:

stack(class T,class Container = deque<T>):创建元素类型为T的空栈,默认容器为deque。

也可以直接使用:stack<ElemType>name 创建类型为ElemType的栈name

(2)操作函数:

bool empty():如果栈为空返回true,否则返回false;

int size():返回栈中元素个数;

void push(const T& t):把元素t压入栈顶;

void pop():当栈非空的情况下,删除栈顶元素;

T& top():当栈非空的情况下,返回栈顶元素。

题解代码:

#include<iostream>
#include<string>
#include<stack>
using namespace std;

int main(void){
    string s1;   //第一个数
    string s2;   //第二个数
    int T = 0;   //总的case数
    cin>>T;
    int count = 1;
    while(count<=T){
        cin>>s1>>s2;
        stack<char>s;
        int l1 = s1.length();
        int l2 = s2.length();
        int maxl = l1;
        int flag = 0;  //进位标志,如果进位则为1,否则为0
        if(l1>=l2){
            maxl = l1;
            string s3(l1-l2,'0');
            s3 = s3 + s2;
            for(int i=maxl-1;i>=0;i--){
                int now = s1[i] + s3[i] + flag - 96;
                if(now>=10){
                    now = now - 10;
                    flag = 1;
                }
                else flag = 0;
                s.push(now + 48);
            }
            if(flag == 1) s.push('1');
        }

        else if(l1<l2){
            maxl = l2;
            string s3(l2-l1,'0');
            s3 = s3 + s1;
            for(int i=maxl-1;i>=0;i--){
                int now = s2[i] + s3[i] + flag - 96;
                if(now>=10){
                    now = now - 10;
                    flag = 1;
                }
                else flag = 0;
                s.push(now + 48);
            }
            if(flag == 1) s.push('1');
        }

        int len = s.size();
        cout<<"Case "<<count<<":"<<endl;
        cout<<s1<<" + "<<s2<<" = ";
        for(int j=0;j<len;j++){
            cout<<s.top();
            s.pop();
        }
        if(count!=T) cout<<endl<<endl;
        else cout<<endl;
        count++;
    }
    return 0;
}

 

1003、Max Sum

此题为“最小子段和”问题,即:

给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时定义子段和为0,依此定义,所求的最优值为: Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n 例如,当(a[1],a[2],a[3],a[4],a[5],a[6])=(-20,11,-4,13,-5,-2)时,最大子段和为20。

宜采递推进行求解,具体可见百度百科提供的思路:

若记b[j]=max(a[i]+a[i+1]+..+a[j]),其中1<=i<=j,并且i<=j<=n。则所求的最大子段和为max b[j],1<=j<=n。
由b[j]的定义可易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。故b[j]的递推方程为:
b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n。
 
 
笔者的源代码如下:
#include<iostream>
#define MAX 100000
using namespace std;

int main(void){
    int T = 0;   //输入行数
    cin>>T;
    int count = 1;
    while(count<=T){
        int h = 1;   //最大子段和的头部
        int h1 = h;  //临时头部
        int r = 1;   //最大子段和的尾部
        int arr[MAX];
        int temp[MAX];  //用于记录扫描每一次arr的sum值
        cin>>arr[0];  //数组长度
        for(int i=1;i<=arr[0];i++){
            cin>>arr[i];
        }
        
        temp[1] = arr[1];
        int max = temp[1];   //最大值
        for(int i=2;i<=arr[0];i++){
            if(temp[i-1]>=0){    //如果上一次扫描的temp大于0,则此次扫描的temp为上一次的加上这一轮的arr
                temp[i] = temp[i-1] + arr[i];
            }
            else{    //否则等于这一次的arr
                temp[i] = arr[i];
                h1 = i;
            }
            if(temp[i]>max){
                max = temp[i];
                h = h1;
                r = i;
            }
        }

        cout<<"Case "<<count<<":"<<endl;
        cout<<max<<" "<<h<<" "<<r;

        if(count!=T) cout<<endl<<endl;
        else cout<<endl;
        count++;
    }
    return 0;

}

 

 
 
 1009、FatMouse' Trade
 
这个题的思路很简单,我可以创建一个结构体Room,用于存放每个仓库的J、F情况,并计算出ratio=J/F。由题意可知,我们应该优先和ratio大的仓库进行交换,因为这样可以花费更少的F去得到更多的J。
因此,将每个仓库按照ratio从大到小进行排序;对于每个仓库,如果此时手上剩余的M大于此仓库的F上限,则交换全部J;否则用手上剩余的全部M交换此仓库的J,并退出循环。
笔者源代码如下:
#include<iostream>
#include<iomanip>
#define MAX 1000
using namespace std;

struct Room{
    double F;
    double J;
    double ratio;
};

int main(void){
    int M,N;
    Room Room[MAX];
    while(cin>>M>>N){
        if(M==-1&&N==-1) break;
        double get_J = 0;
        for(int i=0;i<N;i++){
            cin>>Room[i].J>>Room[i].F;
            Room[i].ratio = Room[i].J / Room[i].F; 
        }

        if(M==0||N==0) {
            cout<<"0.000"<<endl;
            continue;
        }

        //冒泡排序,按ratio从大到小
        for(int i=0;i<N;i++){
            for(int j=1;j<N-i;j++){
                if(Room[j-1].ratio<Room[j].ratio){
                    struct Room temp = Room[j-1];
                    Room[j-1] = Room[j];
                    Room[j] = temp;
                }
            }
        }


        for(int i=0;i<N;i++){
            if(Room[i].F<=M){    //取光此仓库的J需要的F小于目前有的F
                M = M - Room[i].F;
                get_J += Room[i].J;
            }
            else{
                get_J += Room[i].ratio * M;
                break;
            }
        }

        cout<<setprecision(3)<<std::fixed<<get_J<<endl;
    }
        return 0;
}

 

1013、Digital Roots

此题看似很简单,但是却隐含着陷阱,故笔者考虑将此题拿出来起到警示自己的作用。此题的输入规定为整数,但并未对整数范围进行规定,故在解决此类问题时,必须默认输入的整数可能为一个很大的数,故需要使用字符串来表示大整数(与1002题类似)。

但此题如果全部使用字符串来处理所有整数,那么在处理将整数的每一位相加时,会发现很难再次使用字符串来存储这个各个位相加得到的和。

但我们不难发现,将一个大整数的每一位相加得到的整数会比原整数小很多。有多小呢?我们都知道,int类型的数据的最大值为2147483647,我们假设输入的整数每一位都为9(即在整数位数确定的情况下,其各个位的和最大的情况),那么2147483647可表示的上一步整数的最大位数为:2147483647/9 = 238609294位,这已经是一个相当大的位数了。所以在此题中,除了输入的整数采用字符串存储外,其他部分的数据我们采用int类型来存储。

笔者源代码如下:

#include<iostream>
#include<string>
#include<string.h>
using namespace std;

int main(void){
    string num;
    while(cin>>num){
        int len = num.length();
        if(num[0]=='0'&&len==1) break;
        int sum = 0;
        for(int i=0;i<len;i++){
            sum += num[i] - '0';
        }
        while(sum>=10){
            int n = sum;
            sum = 0;
            while(n!=0){
                sum += n % 10;
                n = (n - n%10) / 10;
            }
        }
        cout<<sum<<endl;
    }
    return 0;
}

 

洛谷P1003、铺地毯

一道看上去很复杂的题,实际上我们只需要创造一个存储空间存储所有输入的地毯,然后再倒过来与所求的点匹配即可,如果点在这个地毯,那么输出地毯编号,结束;如果匹配完所有地毯均不在,那么输出-1。那么用什么结构比较好呢?看到“倒这匹配”这几个字就很容易想到——使用栈stack。

 笔者源代码如下:

#include<iostream>
#include<stack>
using namespace std;

typedef struct 
{
    long long a;
    long long b;
    long long g;
    long long k;
}point;

int main(void){
    int n = 0;
    stack<point>pointstack;
    point now;
    long long x,y;
    int i;
    cin>>n;
    for(i=0;i<n;i++){
        cin>>now.a>>now.b>>now.g>>now.k;
        pointstack.push(now);
    }
    cin>>x>>y;
    for(i=n;i>0;i--){
        now = pointstack.top();
        if(x>=now.a&&x<=now.a+now.g&&y>=now.b&&y<=now.b+now.k){
            cout<<i;
            break;
        }
        pointstack.pop();
    }
    if(i==0) cout<<"-1";
    return 0;
}

 

洛谷P1007、独木桥

此题实际上是一道非常简单的题,但是笔者一开始将其想复杂了。。。看了他人的思路之后简直大呼过瘾。我先用自己的理解来讲一下此题的核心思路:

即,我们将每个士兵过河的步数看成一项任务,这样一来,完成所有的步数才是我们最终的任务,因此每个士兵都可以看成一个“工具人”,即他们是没有区别的。当两个士兵相遇时,他们交换他们手中的任务,相遇的两个士兵互相带着对方的“任务”上路了。也就是说,士兵虽然掉头了,但他们身上所带着的任务并没有掉头,每一个士兵的任务都是直线行驶的,每一个时间单位就向初始方向移动一个距离单位。这样一来,只需要找到只需要找到所带任务“最重”的那个士兵的任务是多少步,我们就求出了总时间。至于最短时间和最长时间,我们只需要分别假设每个士兵的任务都是往较近端或较远端,即可求出。

笔者源代码如下:

#include<iostream>
using namespace std;

int main(void){
    int L = 0;
    int N = 0;
    cin>>L>>N;
    int now;
    long min=0,max=0;
    for(int i=0;i<N;i++){
        cin>>now;
        if(L-now+1<now) now = L - now + 1;
        if(min<now) min = now;
        if(max<L-now+1) max = L - now + 1; 
    }
    cout<<min<<" "<<max;
    return 0;
}

 

洛谷P1010、幂次方

遇到这种每一个大问题内带有与大问题具有相同处理方法的小问题的题目,考虑分治法。解决分治问题最常用也是最好理解的就是考虑递归了。

这个题,我们可以做如下步骤考虑,定义f(n)为求n的表达式:

(1)输入一个正整数n,如果n=0,错误,退出;

(2)如果n!=0,求n以2为底的对数,log2(n)=m,并输出一个2;

(3)如果m!=1,则必定后面会有一个左括号,输出一个(;

接下来两步求括号里的数的表达式:

(4)如果m=0,那么输出一个0;

(5)如果m!=1,则继续调用f(m)求m的表达式;

(6)和第三步相照应,如果m!=1,输出一个右括号);

(7)用n-m^2,如果为0,表达式求解完毕;如果不为0,输出一个+号,同时对n-m^2继续求表达式;

笔者源代码如下:

#include<iostream>
#include<cmath>
using namespace std;

void getNum(int n){
    if(n==0) return;
    int now = log2(n);
    cout<<"2";
    if(now!=1) cout<<"(";
    if(now==0) cout<<"0";
    else if(now!=1) getNum(now);
    if(now!=1) cout<<")";
    if(n-pow(2,now)!=0){
        cout<<"+";
        getNum(n-pow(2,now));
    }

    
}

int main(void){
    int n = 0;
    cin>>n;
    getNum(n);
    return 0;

}