【洛谷 | 算法1-1】模拟与高精度 重点题解记录

P1065 作业调度方案

题目描述

我们现在要利用m台机器加工n个工件,每个工件都有m道工序,每道工序都在不同的指定的机器上完成。每个工件的每道工序都有指定的加工时间。

每个工件的每个工序称为一个操作,我们用记号j-k表示一个操作,其中 j 为1到n中的某个数字,为工件号;k为1到m中的某个数字,为工序号,例如2-4表示第2个工件第4道工序的这个操作。在本题中,我们还给定对于各操作的一个安排顺序。

例如,当n=3,m=2时,1-1,1-2,2-1,3-1,3-2,2-2 就是一个给定的安排顺序,即先安排第1个工件的第1个工序,再安排第1个工件的第2个工序,然后再安排第2个工件的第1个工序,等等。

一方面,每个操作的安排都要满足以下的两个约束条件。

  1. 对同一个工件,每道工序必须在它前面的工序完成后才能开始;
  2. 同一时刻每一台机器至多只能加工一个工件。

另一方面,在安排后面的操作时,不能改动前面已安排的操作的工作状态。

由于同一工件都是按工序的顺序安排的,因此,只按原顺序给出工件号,仍可得到同样的安排顺序,于是,在输入数据中,我们将这个安排顺序简写为“1 1 2 3 3 2”。

还要注意,“安排顺序”只要求按照给定的顺序安排每个操作。不一定是各机器上的实际操作顺序。在具体实施时,有可能排在后面的某个操作比前面的某个操作先完成。

例如,取n=3,m=2,已知数据如下(机器号/加工时间):

工件号工序11工序22
11/32/2
21/22/5
32/21/4

则对于安排顺序“1 1 2 3 3 2”,下图中的两个实施方案都是正确的。但所需要的总时间分别是10与12。

img

当一个操作插入到某台机器的某个空档时(机器上最后的尚未安排操作的部分也可以看作一个空档),可以靠前插入,也可以靠后或居中插入。为了使问题简单一些,我们约定:在保证约束条件(1)(2)的条件下,尽量靠前插入。并且,我们还约定,如果有多个空档可以插入,就在保证约束条件(1)(2)的条件下,插入到最前面的一个空档。于是,在这些约定下,上例中的方案一是正确的,而方案二是不正确的。

显然,在这些约定下,对于给定的安排顺序,符合该安排顺序的实施方案是唯一的,请你计算出该方案完成全部任务所需的总时间。

输入格式

第1行为两个正整数 m, n,用一个空格隔开, (其中m(<20)表示机器数,n(<20)表示工件数)

第2行:m × n个用空格隔开的数,为给定的安排顺序。

接下来的2n行,每行都是用空格隔开的m个正整数,每个数不超过20。

其中前n行依次表示每个工件的每个工序所使用的机器号,第1个数为第1个工序的机器号,第2个数为第2个工序机器号,等等。

后n行依次表示每个工件的每个工序的加工时间。

可以保证,以上各数据都是正确的,不必检验。

输出格式

1个正整数,为最少的加工时间。

输入输出样例

输入#1输出#1
2 3
1 1 2 3 3 2
1 2
1 2
2 1
3 2
2 5
2 4
10

说明/提示

NOIP 2006 提高组 第三题

题解

分析

题目较长且容易产生误解(正是在下),认真读完题目并将两张图片看懂,理解两张图片中所显示的安排方式。首先提取题干信息并进行抽象,给定n个工件,每个工件有m道工序,同一工件的每道工序对应不同的一台机器,共m台机器,并给定每道工序的加工时间,需要求的是最短加工时间。需要求最短加工时间,就需要建立时间线,由于同一时刻可能有多台机器在同时加工,所以需要每一台机器都建立一条时间线,即m条时间线,所求即为m台机器中最长的时间线长度。因为有给定“安排顺序”,“安排顺序”只是将工件工序插入到对应机器时间线上的顺序,并不是工序实际加工的顺序,所以可能存在“安排顺序”中后面的工序插入到“安排工序”中前面的工序之前进行加工的情况(即方案一),并不是一定将该工序添加在时间线末尾(即方案二)。

思路

初始对每一台机器建立一条长度为0的时间线,然后循环取出“安排顺序”中的一道工序进行安排,在对应时间线上进行插入,若该时间线上存在长度大于等于该工序加工时间的空档(即时间线上已经存在但是没有任何工序占用的时间段),则将该工序插入到该空档中;若该时间线上不存在合适的空档,则将该工序添加至该时间线末尾,并将该时间线延长满足能该工序添加至末尾的最小长度。插入工序时还需要满足题目中的两个约束条件:约束1中,通过记录每个工件下一次应该加工的工序号实现工件的每道工序按顺序加工,通过将每个工件上一道工序加工结束的时间点作为下一道工序开始安排的起点来实现每道工序一定在上一道工序结束之后开始;约束2中,在一条时间线上,每单位长度的时间段如果有工序已经占用,就用工件号标识此单位时间段,如果没有工序占用,则用0标识为单位长度的空档。

实现

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

struct machining
{
    vector<int> mnum;//每道工序使用的机器号
    vector<int> time;//每道工序花费的时间
};

int m,n;//m:机器数,n:工件数
vector<int> odr;//安排顺序
vector<machining> wp;//工件集合
vector<vector<int>> taixs;//各个机器的时间轴
vector<int> proc;//记录每个工件下一个应该加工的工序
vector<int> lt;//每个工件下一道工序的最小起始时间

void init()
{
    //初始化odr
    for (int i = 0; i < m*n; i++)
    {
        int x;
        cin>>x;
        odr.push_back(x);
    }
    //初始化wp.mnum
    for (int i = 0; i < n; i++)
    {
        machining tmp;
        for (int j = 0; j < m; j++)
        {
            int x;
            cin>>x;
            tmp.mnum.push_back(x);
        }
        wp.push_back(tmp);
    }
    //初始化wp.time
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            int x;
            cin>>x;
            wp[i].time.push_back(x);
        }
    }
    //初始化taxis
    for (int i = 0; i < m; i++)
    {
        vector<int> tmp;
        taixs.push_back(tmp);
    }
    //初始化proc
    for (int i = 0; i < n; i++)
    {
        proc.push_back(1);
    }
    //初始化lt
    for (int i = 0; i < n; i++)
    {
        lt.push_back(0);
    }
}

int main()
{
    cin>>m>>n;
    init();
    //1.循环安排顺序
    for (int k = 0; k < m*n; k++)
    {
        int wpnum = odr[k]-1;//工件号-1
        int pnum = proc[wpnum]-1;//工序号-1
        int mac = wp[wpnum].mnum[pnum]-1;//机器号-1
        int t = wp[wpnum].time[pnum];//该工序所用时间;
        int sz = taixs[mac].size();//该机器时间轴长度
        int least = lt[wpnum];//该工序最小的起始时间

        int rd = 0;//记录机器一个空档的开始
        int start = 1;//判断下一个0是否为空档的开始
        int cnt = 0;//空档长度
        int fini = 0;//是否插入成功
        //2.循环机器时间轴
        for (int i = least; i < sz; i++)
        {
            if (taixs[mac][i] == 0)
            {
                if (start)//是一个空档的开始
                {
                    rd = i;
                    start = 0;
                    cnt = 0;
                }
                cnt++;
                if (cnt >= t)//该工序可以插入此空档
                {
                    //3.将空档填上工件号
                    for (int j = rd; j < rd+t; j++)
                    {
                        taixs[mac][j] = wpnum + 1;
                    }
                    lt[wpnum] = rd + t;//下一道工序只能在该工序结束之后开始
                    fini = 1;//插入成功
                    break;
                }
            }
            else
            {
                start = 1;
            }
        }
        if (!fini)//插入失败,则添加到时间轴最后
        {
            if (start)//时间轴最后不存在空档
            {
                //强制加入空档使该工序在上一道工序之后开始
                for (int i = 0; i < least-sz; i++)
                {
                    taixs[mac].push_back(0);
                }
                for (int i = 0; i < t; i++)
                {
                    taixs[mac].push_back(wpnum+1);
                }
            }
            else//时间轴最后存在空档(本题中好像不存在该种情况)
            {
                for (int i = rd; i < rd+t; i++)
                {
                    if (i <= sz-1)//在最后的空档中
                    {
                        taixs[mac][i] = wpnum + 1;
                    }
                    else//不在空档中,需要增长时间轴
                    {
                        taixs[mac].push_back(wpnum+1);
                    }
                }
            }
            lt[wpnum] = taixs[mac].size();
        }
        proc[wpnum]++;
    }
    int max = 0;
    for (int i = 0; i < m; i++)
    {
        if (max == 0 || taixs[i].size() > max)
        {
            max = taixs[i].size();
        }
    }
    cout<<max;

    return 0;
}

输入/输出

PS F:\VSC\exercise> cd "f:\VSC\exercise\" ; if ($?) { g++ Untitled-1.cpp -o Untitled-1 } ; if ($?) { .\Untitled-1 }
2 3
1 1 2 3 3 2
1 2 
1 2 
2 1
3 2
2 5
2 4
10

P1045 麦森数

题目描述

形如2P−1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2P−1不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。

任务:从文件中输入P(1000<P<3100000),计算2P−1的位数和最后500位数字(用十进制高精度数表示)

输入格式

文件中只包含一个整数P(1000<P<3100000)

输出格式

第一行:十进制高精度数2P−1的位数。

第2-11行:十进制高精度数2P−1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0)

不必验证2P−1与P是否为素数。

输入输出样例

输入#1输出#1
1279386
00000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000 00000000000000104079321946643990819252403273640855 38615262247266704805319112350403608059673360298012 23944173232418484242161395428100779138356624832346 49081399066056773207629241295093892203457731833496 61583550472959420547689811211693677147548478866962 50138443826029173234888531116082853841658502825560 46662248318909188018470682222031405210266984354887 32958028878050869736186900714720710555703168729087

说明/提示

【题目来源】

NOIP 2003 普及组第四题

题解

分析

因为1000<P<3100000,所以只能用高精度来解题。题目分两问,第一问比较简单,因为2n中最后一位不可能为0,所以2P-1和2P的位数是相同的。又因为2P是固定形式,所以可以利用求位数的公式来解出位数。第二问中,需要求得末尾500位的数字,即使用高精度的方法进行运算得到2P的末尾500然后再减一即为所求。

思路

第一问可用公式求解,2n的位数k公式为$ k = 1+log_{10}(2^n)$;第二问中,高精度的计算需要利用数组来存储每个数,然后定义高精乘和高精加来进行运算,因为只需要末尾500位,所以数组长度设为500即可。需要注意的是,由于P的值较大,所以单纯的将所求数组m循环乘2来实现幂运算的方式会用时过长导致TLE,所以需要进行过程优化,方法为快速幂,即可将幂运算的时间复杂度从O(N)缩减到O(log2N),从而AC。

实现

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

//初始化数组
void init(int a[], int sz)
{
    for (int i = 0; i < sz; i++)
    {
        a[i] = 0;
    }
}

//打印数组
void pf(int a[], int sz)
{
    int flag = 0;
    for (int i = 0; i < sz; i++)
    {
        if (a[i] != 0)
        {
            flag = 1;
        }
        if (flag)
        {
            cout<<a[i];
        }
    }
    cout<<endl;
}

//大数和大数之间的高精加
void iplus(int a[], int b[], int sz)
{
    int p = 0;
    for (int i = sz - 1; i >= 0; i--)
    {
        a[i] = a[i] + b[i] + p;
        if (a[i] >= 10)
        {
            p = a[i] / 10;
            a[i] = a[i] % 10;
        }
        else
        {
            p = 0;
        }
    }
}

//大数和小数之间的高精乘
void mult(int a[], int n, int sz)
{
    int c = 0;
    for (int i = sz - 1; i >= 0; i--)
    {
        a[i] = a[i] * n + c;
        if (a[i] >= 10)
        {
            c = a[i] / 10;
            a[i] %= 10;
        }
        else
        {
            c = 0;
        }
    }
}

//大数和大数之间的高精乘
void mult(int a[], int b[], int sz)
{
    int t[1000] = {};
    for (int i = sz-1; i >= 0; i--)
    {
        int c = 0;
        for (int j = sz-1; j >= 0; j--)
        {
            t[i+j+1] += a[i] * b[j] + c;
            if (t[i+j+1] >= 10)
            {
                c = t[i+j+1] / 10;
                t[i+j+1] = t[i+j+1] % 10;
            }
            else
            {
                c = 0;
            }
        }
    }
    for (int i = 0; i < sz; i++)
    {
        a[i] = t[i+500];
    }
}

//高精度中的快速幂方法
void ipow(int p, int m[], int sz)
{
    if (p > 1)
    {
        int tmp[sz];
        init(tmp,sz);
        for (int i = 0; i < sz; i++)
        {
            tmp[i] = m[i];
        }
        mult(m,m,sz);
        if (p % 2 != 0)
        {
            ipow(p/2,m,sz);
            mult(m,tmp,sz);
        }
        else
        {
            ipow(p/2,m,sz);
        }
    }
}

int main()
{
    int p;
    cin >> p;
    int fig = 1 + (double)(log10(2) * p);
    cout << fig << endl;
    int m[500];
    int sz = 500;
    init(m,sz);
    m[sz - 1] = 1;
    mult(m, 2, sz);
    ipow(p,m,sz);
    m[sz - 1]--;
    int cnt = 0;
    for (int i = 0; i < sz; i++)
    {
        cout << m[i];
        cnt++;
        if (cnt == 50)
        {
            cnt = 0;
            cout << endl;
        }
    }

    return 0;
}

输入/输出

PS F:\VSC\exercise> cd "f:\VSC\exercise\" ; if ($?) { g++ Untitled-1.cpp -o Untitled-1 } ; if ($?) { .\Untitled-1 }
2000
603
44596463898746277344711896086305533142593135616665
31853912998914531228000068877914824004487142892699
00634862447816154636463883639473170260404663539709
04996558162398808944629605623311649536164221970332
68134416890898445850560237948480791405890093477650
04290027167066258305220081322362812917612678833172
06598995396418127021779858404042159853183251540889
43390209192055495778358967203916008195721663058275
53804255837260155283487864194320545089152757838826
25175435528800822842770817965453762184851149029375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值