[Usaco2018 Feb]Slingshot 多种实现-附完整代码

 
  

原题参考:http://www.usaco.org/index.php?page=viewproblem2&cpid=816&lang=zh

问题说明

FarmerJohn最讨厌的农活是运输牛粪。为了精简这个过程,他产生了一个新奇的想法:与其使用拖拉机拖着装满牛粪的大车从一个地点到另一个地点,为什么不用一个巨大的便便弹弓把牛粪直接发射过去呢?(事实上,好像哪里不太对……)FarmerJohn的农场沿着一条长直道路而建,所以他农场上的每个地点都可以简单地用该地点在道路上的位置来表示(相当于数轴上的一个点)。FJ建造了N个弹弓(1≤N≤10^5),其中第i个弹弓可以用三个整数xi,yi以及ti描述,表示这个弹弓可以在ti单位时间内将牛粪从位置xi发射到位置yi。FJ有M堆牛粪需要运输(1≤M≤10^5)。第j堆牛粪需要从位置aj移动到位置bj。使用拖拉机运输牛粪,经过路程d需要消耗d单位时间。FJ希望通过对每一堆牛粪使用至多一次弹弓来减少运输时间。FJ驾驶没有装载牛粪的拖拉机的时间不计。对这M堆牛粪的每一堆,在FJ可以在运输过程中使用至多一次弹弓的条件下,帮助FJ求出其最小运输时间。
 
  

输入格式(文件名:slingshot.in):

输入的第一行包含N和M。下面N行,每行用xi,yi,ti(0≤xi,yi,ti≤10^9)描述了一个弹弓。最后M行用aj和bj描述了需要移动的牛粪。
 
  

输出格式(文件名:slingshot.out):

输出M行,每堆牛粪一行,表示运输这堆牛粪需要的最短时间。
 
  

输入样例:

3

0 10 1

13 8 2 

12

5 2 

20 7

输出样例:

4

3

10

在这里,第一堆牛粪需要从位置1移动到位置12。不使用弹弓的话,会消耗11单位时间。但是,如果使用第一个弹弓,只需消耗1单位时间移动到位置0(弹弓的发射点),1单位时间将弹弓通过空中弹射并着陆在位置10(弹弓的目的地),然后2单位时间把牛粪移动到位置12。第二堆牛粪的最佳移动方案是不使用弹弓,第三堆牛粪应该使用第二个弹弓。

实现

暴力算法(PHP版本)

 $line) {
            $line_split = preg_split("/\s+/", $line); //多个空格分割

            if ($idx < 1) {
                $this->_nm($n, $m, $idx, $line_split); //第一行,构建 n, m
            } else if ($idx <= $n   ) {
                $this->_slingshot($idx, $line_split); // 1< i< n,构建弹弓数组
            } else if ($idx <= $n + $m ) {
                $this->_cowdung($idx, $line_split); // n < i < m,构建牛粪数组
            } else {
                break; //忽略,其他行
            }
        }

        if (!empty($this->error)) {
            throw new Exception(implode("\r\n", $this->error));
        }

        $this->n = $n;
        $this->m = $m;
    }

    /**
     * 构建变量: $n 和 $m
     * @param: $n int return 
     * @param: $m int return
     * @param: $idx int  当前行号,异常时给出行号信息
     * @param: $line_split = array(int,int) 
    */
    private function _nm(&$n, &$m, $idx, $line_split)
    {
        if (count($line_split) != 2) {
            array_push($this->error, '第[' . $idx . ']行格式不对: 只能包含n m 2个数字并且以空格分隔');
        } else {
            $n = $line_split[0];
            $m = $line_split[1];
        }
    }

    /**
     * 构建类变量: $this->slingshot_desc
     * @param: $idx int  当前行号,异常时给出行号信息
     * @param: $line_split = array(int,int)
    */
    private function _slingshot($idx, $line_split)
    {
        if (count($line_split) != 3) {
            array_push($this->error, '第[' . $idx. ']行格式不对: 只能包含x y t 3个数字并且以空格分隔');
        } else {
            array_push($this->slingshot_desc, $line_split);
        }
    }

    /**
     * 构建类变量:$this->cow_dung_position
     * @param: $idx int  当前行号,异常时给出行号信息
     * @param: $line_split = array(int,int)
    */
    private function _cowdung($idx, $line_split)
    {
        if (count($line_split) != 2) {
            array_push($this->error, '第[' . $idx. ']行格式不对: 只能包含a b 2个数字并且以空格分隔');
        } else {
            array_push($this->cow_dung_position, $line_split);
        }
    }

    /**
     * 步数计算,核心思想是:
     * 1. 先计算出不使用弹弓的步数作为基准值 $base
     * 2. 依次使用弹弓,分别计算出从起点到弹弓传送门起点和终点所需要的步数,再加上运送弹弓所需要的时间,分别为:$first、$second
     * 3. $base, $first, $second 3 者取最小值,并作为新的基准值
     * 4. 弹弓遍历完成后,最后的$base 则为最小步数
    */
    public function cal()
    {
        foreach ($this->cow_dung_position as $position) {
            $base = abs($position[0] - $position[1]); //基准值,不使用弹弓需要的步数

            foreach ($this->slingshot_desc as $slingshot) {
                $first = abs($position[0] - $slingshot[0]) + abs($position[1] - $slingshot[1]) + $slingshot[2];
                $second = abs($position[0] - $slingshot[1]) +  abs($position[1] - $slingshot[0]) + $slingshot[2];

                $base = $base < $first ?($base < $second ? $base : $second) : ($first < $second ? $first : $second);
            }
            array_push($this->mininum_steps, $base);
        }
    }
    
   /**
    * 将类变量参数$this->mininum_steps 写入指定的文件
    * @param: $filename string
   */
    public function save($filename)
    {
        file_put_contents($filename, implode("\r\n", $this->mininum_steps));    
    }
}
try {
    //第一种方法:暴力计算
    $classname = new SlingshotBase('./slingshot.in');
    $classname->cal();
    $classname->save('./slingshot.out');
} catch (Exception $e) {
    echo $e->getMessage();
}
?>

树形数组(PHP版本)

 $line) {
            $line_split = preg_split("/\s+/", $line); //多个空格分割

            if ($idx < 1) {
                $this->_nm($n, $m, $idx, $line_split); //第一行,构建 n, m
            } else if ($idx <= $n   ) {
                $this->_slingshot($idx, $line_split); // 1< i< n,构建弹弓数组
            } else if ($idx <= $n + $m ) {
                $this->_cowdung($idx, $line_split); // n < i < m,构建牛粪数组
            } else {
                break; //忽略,其他行
            }
        }

        if (!empty($this->error)) {
            throw new Exception(implode("\r\n", $this->error));
        }

        $this->n = $n;
        $this->m = $m;
    }

    /**
     * 构建变量: $n 和 $m
     * @param: $n int return 
     * @param: $m int return
     * @param: $idx int  当前行号,异常时给出行号信息
     * @param: $line_split = array(int,int) 
    */
    private function _nm(&$n, &$m, $idx, $line_split)
    {
        if (count($line_split) != 2) {
            array_push($this->error, '第[' . $idx . ']行格式不对: 只能包含n m 2个数字并且以空格分隔');
        } else {
            $n = $line_split[0];
            $m = $line_split[1];
        }
    }

    /**
     * 构建类变量: $this->slingshot_desc
     * @param: $idx int  当前行号,异常时给出行号信息
     * @param: $line_split = array(int,int)
    */
    private function _slingshot($idx, $line_split)
    {
        if (count($line_split) != 3) {
            array_push($this->error, '第[' . $idx. ']行格式不对: 只能包含x y t 3个数字并且以空格分隔');
        } else {
            array_push($this->slingshot_desc, $line_split);
        }
    }

    /**
     * 构建类变量:$this->cow_dung_position
     * @param: $idx int  当前行号,异常时给出行号信息
     * @param: $line_split = array(int,int)
    */
    private function _cowdung($idx, $line_split)
    {
        if (count($line_split) != 2) {
            array_push($this->error, '第[' . $idx. ']行格式不对: 只能包含a b 2个数字并且以空格分隔');
        } else {
            array_push($this->cow_dung_position, $line_split);
        }
    }

    /**
     * 步数计算,核心思想是:
     * 1. 先计算出不使用弹弓的步数作为基准值 $base
     * 2. 依次使用弹弓,分别计算出从起点到弹弓传送门起点和终点所需要的步数,再加上运送弹弓所需要的时间,分别为:$first、$second
     * 3. $base, $first, $second 3 者取最小值,并作为新的基准值
     * 4. 弹弓遍历完成后,最后的$base 则为最小步数
    */
    public function cal()
    {
        foreach ($this->cow_dung_position as $position) {
            $base = abs($position[0] - $position[1]); //基准值,不使用弹弓需要的步数

            foreach ($this->slingshot_desc as $slingshot) {
                $first = abs($position[0] - $slingshot[0]) + abs($position[1] - $slingshot[1]) + $slingshot[2];
                $second = abs($position[0] - $slingshot[1]) +  abs($position[1] - $slingshot[0]) + $slingshot[2];

                $base = $base < $first ?($base < $second ? $base : $second) : ($first < $second ? $first : $second);
            }
            array_push($this->mininum_steps, $base);
        }
    }
    
   /**
    * 将类变量参数$this->mininum_steps 写入指定的文件
    * @param: $filename string
   */
    public function save($filename)
    {
        file_put_contents($filename, implode("\r\n", $this->mininum_steps));    
    }
}

/**
*/
class TreeArray extends SlingshotBase
{
    /**
     * @var 对比数据最大值
    */
    public $infinite = 10000000000; //可定义成其他

    /**
     * @var 树形数组个数,弹弓y 和 牛粪 b 数组合并,排序,去重后的数组个数
    */
    public $yb = 0;

    /**
     * @var 树形数组
    */
    public $tree_array = array();

    /**
     * @var 弹弓数组
    */
    public $slingshot = array();

    /**
     * @var 牛粪数组
    */
    public $cowdung = array();

    /**
     * 最大值数组初始化
    */
    private function __init_src()
    {
        $src = array();
        $that = clone $this;

        for ($i = 1; $i <= $this->yb; $i++) {
            $src[$i] = $that->infinite;
        }

        return $src;
    }
    /**
     * @类变量初始化
    */
    public function init()
    {
        $tree_array = array(); //树形数组
        $base_steps = array(); //基准耗时数组

        $slingshot = array(array(
            'x' => 0,
            'y' => 0,
            't' => 0,
        ));
        foreach ($this->slingshot_desc as $idx => $shot) {
            $idx += 1;
            $slingshot[$idx] = array(
                'x' => $shot[0],
                'y' => $shot[1],
                't' => $shot[2],
            );
            $tree_array[$idx] = $shot[1];
        }

        $cowdung = array(array(
            'a' => 0,
            'b' => 0,
            'id' => 0, 
        ));
        foreach ($this->cow_dung_position as $idx => $position) {
            $idx += 1;
            $cowdung[$idx] = array(
                'a' => $position[0],
                'b' => $position[1],
                'id' => $idx,
            );
            array_push($tree_array, $position[1]);
            $base_steps[$idx] = abs($position[0] - $position[1]);
        }

        sort($slingshot); //排序,默认根据数组第一个字段排序
        sort($cowdung); // 排序默认根据第一个字段值
        $tree_array = array_unique($tree_array);
        sort($tree_array);
        
        $that = clone $this;
        foreach ($cowdung as &$dung) {
            $dung['bb'] = $that->lower_bound($tree_array, $dung['b']) + 1;
        }
        foreach ($slingshot as $idx => &$sling) {
            $sling['yy'] = $that->lower_bound($tree_array, $sling['y']) + 1;
        }

        $this->cowdung = $cowdung;
        $this->slingshot = $slingshot;
        $this->tree_array = array_merge($this->tree_array, $tree_array);
        $this->mininum_steps = $base_steps;
        $this->yb = count($tree_array) ;
        $this->tree_array = $tree_array;
    }

    /**
     * 重写父类的计算方法,采用树形数组实现最小值计算和查询
     * 对于 |x-a| + |y-b| + t,按照数学公式,分4种情况讨论:
     * 1. 对于 x >= a && y >= b, 等价于 x-a+y-b+t = -a-b(固定) + (x+y+t)(树形数组计算)
     * 2. 对于 x >= a && y <= b, 等价于 x-a+b-y+t = -a+b(固定) + (x-y+t)(树形数组计算)
     * 3. 对于 x < a && y <= b, 等价于 a-x+y-b+ t = a-b(固定) + (-x+y+t)(树形数组计算)
     * 4. 对于 x < a && y >= b, 等价于 a-x +b - y + t = a+b(固定) + (-x-y+t)(树形数组计算)
    */
    public function cal()
    {
        $sl = $this->slingshot;
        $qr = $this->cowdung;
        $m = $this->m;
        $n = $this->n;
        $nn = $this->yb;
        $ans = $this->mininum_steps;
        $that = clone $this;

        //第一种情况: x <= a && y <= b
        $now = 1;
        $src = $this->__init_src();
        for ($i = 1; $i <= $m; $i++) {
            while ($now <= $n && $sl[$now]['x'] <= $qr[$i]['a']) {
                $that->add($src, $sl[$now]['yy'], -$sl[$now]['x'] - $sl[$now]['y'] + $sl[$now]['t']);
                ++$now;
            }

            $ans[$qr[$i]['id']] = min($ans[$qr[$i]['id']], $qr[$i]['a'] + $qr[$i]['b'] + $that->query($src, $qr[$i]['bb']));
        }

        //第二种情况: x<=a  && y>=b
        $now = 1;
        $src = $this->__init_src();
        for ($i = 1; $i <= $m; $i++) {
            while ($now <= $n && $sl[$now]['x'] <= $qr[$i]['a']) {
                $that->add($src, $nn - $sl[$now]['yy'] + 1, -$sl[$now]['x'] + $sl[$now]['y'] + $sl[$now]['t']);
                ++$now;
            }

            $ans[$qr[$i]['id']] = min($ans[$qr[$i]['id']], $qr[$i]['a'] - $qr[$i]['b'] + $that->query($src, $nn- $qr[$i]['bb'] + 1));
        }

        //第三种情况: x> a  && y < b
        $now = $n;
        $src = $this->__init_src();
        for ($i = $m; $i >= 1; --$i) {
            while ($now >= 1 && $sl[$now]['x'] > $qr[$i]['a']) {
                $that->add($src, $sl[$now]['yy'], $sl[$now]['x'] - $sl[$now]['y'] + $sl[$now]['t']);
                --$now;
            }

            $ans[$qr[$i]['id']] = min($ans[$qr[$i]['id']], -$qr[$i]['a'] + $qr[$i]['b'] + $that->query($src, $qr[$i]['bb']));
        }

        //第四种情况: x> a && y> b
        $now = $n;
        $src = $this->__init_src();
        for ($i = $m; $i >= 1; --$i) {
            while ($now >= 1 && $sl[$now]['x'] > $qr[$i]['a']) {
                $that->add($src, $nn - $sl[$now]['yy'] + 1, $sl[$now]['x'] + $sl[$now]['y'] + $sl[$now]['t']);
                --$now;
            }

            $ans[$qr[$i]['id']] = min($ans[$qr[$i]['id']], -$qr[$i]['a']  +- $qr[$i]['b'] + $that->query($src, $nn - $qr[$i]['bb'] + 1));
        }

        $this->mininum_steps = $ans;
    }

    /**
     * 使用机器补码特性,计算2 ^ x
     * @param: $x int
     * @return: int
    */
    public function lowbit($x)
    {
        return $x & -$x;
    }

    /**
     * 树形数组,取最小值
     * @param: $src array 最大值数组, 会被修改
     * @param: $x int,
     * @param: $v int;
    */
    public function add(&$src, $x, $v)
    {
        for (; $x <= $this->yb; $x += $this->lowbit($x)) $src[$x] = min($src[$x], $v);
    }

    /**
     * 树形数组,遍历查询,取出最小值
     * @param: $src array 最大值数组
     * @param: $x int
    */
    public function query($src, $x)
    {
        $tmp = $this->infinite;

        for (; $x ; $x -= $this->lowbit($x))  $tmp = min($tmp, $src[$x]);

        return $tmp;
    }
    /**
     * 在源数组中查找指定值,返回大于或等于v的第一个元素位置。如果不存在,则返回数组的最大位置
     * @param: $src_array array
     * @param: $v mixed
     * @return: int
    */
    public function lower_bound($src_array, $v)
    {
        $idx = array_search($v, $src_array);
        
        if ($idx !== false) {
            return $idx;
        } else {
            return count($src_array);
        }
    }
}
try {
    //第一种方法:暴力计算
    $classname = new SlingshotBase('./slingshot.in');
    $classname->cal();
    $classname->save('./slingshot.out');

    //第二种方法:树形数组
    $tree_array = new TreeArray('./slingshot.in');
    $tree_array->init();
    $tree_array->cal();
    $tree_array->save('slingshot.out.ta');

} catch (Exception $e) {
    echo $e->getMessage();
}
?>

多进程暴力算法(Python版本)

#!/usr/bin/python
# -*- coding: utf-8 -*-

from multiprocessing import Pool

#读取文件内容按照指定格式
def file_reader(filename):
    with open(filename) as file_object:
        contents = file_object.readlines()
    
    line_one = contents[0].split()
    n = int(line_one[0])
    m = int(line_one[1])

    slingshot = []
    cowdung = []
    for line in contents[1:n+1]:
        slingshot.append(line.split())
    for line in contents[n+1:n+m+1]:
        cowdung.append(line.split())

    return slingshot, cowdung

#将list表单数据写入指定文件中
def file_writter(filename, lines):
    file = open(filename, 'w')
    for line in lines:
        file.write(str(line) + '\r\n')
    file.close()

#核心算法,暴力计算所有可能
def cal(data):
    (i, slingshot, cowdung) = data
    steps = []

    for cow in cowdung:
        base = abs(int(cow[0]) - int(cow[1]))

        for shot in slingshot:
            t1 = abs(int(cow[0]) - int(shot[0]))
            t2 = abs(int(cow[1]) - int(shot[1]))
            first = sum([t1, t2, int(shot[2])])
               
            t3 = abs(int(cow[0]) - int(shot[1]))
            t4 = abs(int(cow[1]) - int(shot[0]))
            second = sum([t3, t4, int(shot[2])])
        
            base = min(base, first, second)
    
        steps.append(base)

    return [i, steps]

if __name__ == '__main__':
    (slingshot, cowdung) = file_reader('./slingshot.in')
        
    total = len(cowdung)
    pools = 5
    avg = total / pools
    data = [];

    #不同进程处理不同的分片数据
    start = 0
    end = start
    for i in range(pools):
        start = end
        end = start + avg
        next = i + 1

        if next == pools:
            splitcow = cowdung[start:]
        else:
            splitcow = cowdung[start:end]

        data.append([i, slingshot, splitcow])
    
    #多线程池,并将结果按照进程序号排序,避免乱序
    p = Pool(pools)
    steps = p.map(cal, data)
    steps = sorted(steps)
    
    #将计算结果写入指定文件
    dstlines = []
    for step in steps:
        (sn, result)  = step
        dstlines.extend(result)
    file_writter('./slingshot.out', dstlines)



你可能感兴趣的:([Usaco2018 Feb]Slingshot 多种实现-附完整代码)