Problem:
Score : 300 points

Problem Statement

A programming competition site AtCode provides algorithmic problems.
Each problem is allocated a score based on its difficulty.
Currently, for each integer i between 1 and D (inclusive), there are p_i problems with a score of 100i points.
These p_1 + … + p_D problems are all of the problems available on AtCode.

A user of AtCode has a value called total score.
The total score of a user is the sum of the following two elements:

Base score: the sum of the scores of all problems solved by the user.

Perfect bonuses: when a user solves all problems with a score of 100i points, he/she earns the perfect bonus of c_i points, aside from the base score (1 ≤ i ≤ D).

Takahashi, who is the new user of AtCode, has not solved any problem.
His objective is to have a total score of G or more points.
At least how many problems does he need to solve for this objective?

Constraints

1 ≤ D ≤ 10

1 ≤ p_i ≤ 100

100 ≤ c_i ≤ 10^6

100 ≤ G

All values in input are integers.

c_i and G are all multiples of 100.

It is possible to have a total score of G or more points.

Input

Input is given from Standard Input in the following format:

D G
p_1 c_1
:
p_D c_D

Output

Print the minimum number of problems that needs to be solved in order to have a total score of G or more points. Note that this objective is always achievable (see Constraints).

Sample Input 1

2 700
3 500
5 800

Sample Output 1

3

In this case, there are three problems each with 100 points and five problems each with 200 points. The perfect bonus for solving all the 100-point problems is 500 points, and the perfect bonus for solving all the 200-point problems is 800 points. Takahashi's objective is to have a total score of 700 points or more.

One way to achieve this objective is to solve four 200-point problems and earn a base score of 800 points. However, if we solve three 100-point problems, we can earn the perfect bonus of 500 points in addition to the base score of 300 points, for a total score of 800 points, and we can achieve the objective with fewer problems.

Sample Input 2

2 2000
3 500
5 800

Sample Output 2

7

This case is similar to Sample Input 1, but the Takahashi's objective this time is 2000 points or more. In this case, we inevitably need to solve all five 200-point problems, and by solving two 100-point problems additionally we have the total score of 2000 points.

Sample Input 3

2 400
3 500
5 800

Sample Output 3

2

This case is again similar to Sample Input 1, but the Takahashi's objective this time is 400 points or more. In this case, we only need to solve two 200-point problems to achieve the objective.

Sample Input 4

5 25000
20 1000
40 1000
50 1000
30 1000
1 1000

Sample Output 4

66

There is only one 500-point problem, but the perfect bonus can be earned even in such a case.

Solution in Rust (1.15.1) (judge verdict: Accepted)

use std::cmp;
use std::cmp::Ordering;
use std::fmt::Debug;
#[allow(unused_imports)]
use std::fs::File;
#[allow(unused_imports)]
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::str::FromStr;

pub fn start<R: Read, W: Write>(input: R, output: &mut W) {
    let mut scanner = Scanner::new(input.bytes().map(Result::unwrap));
    let d = scanner.next_value::<usize>();
    let g = scanner.next_value::<usize>() / 100;
    let arr = scanner
        .next_2darray::<usize>(2, d)
        .iter()
        .map(move |x| [x[0] as i16, (x[1] / 100) as i16])
        .collect::<Vec<_>>();

    // dpテーブルの容量は、2MB
    // 計算量は最大で 2*10^7程度なのでDPでいく
    // 漸化式
    // i番目までで j * 100 点以上とする
    // dp[i][0] = 0
    // dp[0][j] = 32767
    // dp[i + 1][j] = min { dp[i][j], dp[i][j - k * (i + 1)] + k | k < arr[i][0], dp[i][j - arr[i][0] * (i + 1) - arr[i][1]] + arr[i][0] }
    // 枝刈り dp[i][j] = 32767 ならば dp[i][j + 1] = 32767

    let mut dp = vec![vec![i16::max_value(); g + 1]; d + 1];
    dp[0][0] = 0;
    fn sub0(a: usize, b: usize) -> usize {
        if a < b {
            0
        } else {
            a - b
        }
    }
    fn add0(a: i16, b: i16) -> i16 {
        let r = a.wrapping_add(b);
        if r < 0 {
            i16::max_value()
        } else {
            r
        }
    }

    'outer: for i in 1..(d + 1) {
        dp[i][0] = 0;
        for j in 1..(g + 1) {
            dp[i][j] = cmp::min(
                (0..arr[i - 1][0])
                    .map(|k| add0(dp[i - 1][sub0(j, (k as usize) * i)], k))
                    .min()
                    .unwrap(),
                add0(
                    dp[i - 1][sub0(j, arr[i - 1][0] as usize * i + arr[i - 1][1] as usize)],
                    arr[i - 1][0],
                ),
            );
            if dp[i][j] == i16::max_value() {
                continue 'outer;
            }
        }
    }

    write!(output, "{}\n", dp.iter().map(|x| x[g]).min().unwrap()).unwrap();
}

////////////////////////////
// Tests
////////////////////////////

#[cfg(test)]
mod test {
    use super::start;
    use std::ffi::OsStr;
    use std::fs;
    use std::fs::File;
    use std::io::Cursor;
    use std::io::{BufReader, Read};

    #[test]
    fn example() {
        let mut entries = fs::read_dir("./tests")
            .unwrap()
            .into_iter()
            .flat_map(|x| x.ok())
            .map(|x| x.path())
            .filter(|x| x.extension().unwrap_or(OsStr::new("")).to_string_lossy() == "in")
            .collect::<Vec<_>>();
        entries.sort();
        for entry in entries {
            let mut output = Vec::new();
            BufReader::new(File::open(entry.with_extension("out")).unwrap())
                .read_to_end(&mut output)
                .unwrap();
            let input = BufReader::new(File::open(entry).unwrap());

            let mut out = Cursor::new(Vec::new());
            start(input, &mut out);
            let outvec = out.into_inner();
            println!("{}", String::from_utf8((&outvec).to_vec()).unwrap());
            assert_eq!(outvec, output);
        }
    }
}

////////////////////////////
// Snipetts
////////////////////////////

#[allow(dead_code)]
fn main() {
    let stdin = stdin();
    let stdout = stdout();
    let mut out = BufWriter::new(stdout.lock());
    let input = BufReader::new(stdin.lock());
    start(input, &mut out);
}

// // ファイルの場合
// #[allow(dead_code)]
// fn main() {
//     let mut out = BufWriter::new(File::create("output.txt").unwrap());
//     let input = BufReader::new(File::open("input.txt").unwrap());
//     start(input, &mut out);
// }

// 大きなスタックが必要な場合はこちらを利用する
// #[allow(dead_code)]
// fn main() {
//     std::thread::Builder::new()
//         .name("big stack size".into())
//         .stack_size(32 * 1024 * 1024) // 32 MBのスタックサイズ
//         .spawn(|| {
//             let stdin = stdin();
//             let stdout = stdout();
//             let out = BufWriter::new(stdout.lock());
//             let input = BufReader::new(stdin.lock());
//             start(input, out);
//         })
//         .unwrap()
//         .join()
//         .unwrap();
// }

pub struct Scanner<I: Iterator<Item = u8>> {
    input: I,
}

impl<I: Iterator<Item = u8>> Scanner<I> {
    pub fn new(input: I) -> Self {
        Scanner::<I> { input: input }
    }

    // 文字コードはISO/IEC 8859-1 (Latin-1)とする
    pub fn next_value<T: FromStr>(&mut self) -> T
    where
        <T as FromStr>::Err: Debug,
    {
        T::from_str(
            &(self
                .input
                .by_ref()
                .map(char::from)
                .skip_while(|x| x.is_whitespace())
                .take_while(|x| !x.is_whitespace())
                .collect::<String>()),
        )
        .unwrap()
    }

    // 文字コードはISO/IEC 8859-1 (Latin-1)とする
    #[allow(dead_code)]
    pub fn next_bytes(&mut self) -> Vec<u8> {
        self.input
            .by_ref()
            .skip_while(|&x| char::from(x).is_whitespace())
            .take_while(|&x| !char::from(x).is_whitespace())
            .collect::<Vec<_>>()
    }

    #[allow(dead_code)]
    pub fn next_array<T: FromStr>(&mut self, len: usize) -> Vec<T>
    where
        <T as FromStr>::Err: Debug,
    {
        (0..len).map(|_| self.next_value()).collect()
    }

    #[allow(dead_code)]
    pub fn next_2darray<T: FromStr>(&mut self, x: usize, y: usize) -> Vec<Vec<T>>
    where
        <T as FromStr>::Err: Debug,
    {
        (0..y).map(|_| self.next_array(x)).collect()
    }

    #[allow(dead_code)]
    pub fn next_2d_bytes(&mut self, len: usize) -> Vec<Vec<u8>> {
        (0..len).map(|_| self.next_bytes()).collect()
    }
}

// 1.15.1でReverseを使う
// https://qiita.com/hatoo@github/items/fa14ad36a1b568d14f3e
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Reverse<T>(pub T);

impl<T: PartialOrd> PartialOrd for Reverse<T> {
    fn partial_cmp(&self, other: &Reverse<T>) -> Option<Ordering> {
        other.0.partial_cmp(&self.0)
    }
}

impl<T: Ord> Ord for Reverse<T> {
    fn cmp(&self, other: &Reverse<T>) -> Ordering {
        other.0.cmp(&self.0)
    }
}

// PartialOrdをTotalOrdにするラッパ
// https://qiita.com/hatoo@github/items/fa14ad36a1b568d14f3e

#[derive(PartialEq, PartialOrd)]
pub struct Total<T>(pub T);

impl<T: PartialEq> Eq for Total<T> {}

impl<T: PartialOrd> Ord for Total<T> {
    fn cmp(&self, other: &Total<T>) -> Ordering {
        self.0.partial_cmp(&other.0).unwrap()
    }
}

// 順列生成
#[allow(dead_code)]
pub fn permutations(n: usize, k: usize) -> Box<Iterator<Item = Vec<u8>>> {
    fn inner(array: Vec<u8>, depth: usize, max: u8) -> Box<Iterator<Item = Vec<u8>>> {
        let na = (0..max).flat_map(move |x| {
            if array.contains(&x) {
                None
            } else {
                let mut newarray = array.clone();
                newarray.push(x);
                Some(newarray)
            }
        });
        if depth > 0 {
            Box::new(na.flat_map(move |x| inner(x, depth - 1, max)))
        } else {
            Box::new(na)
        }
    }
    inner(Vec::new(), k - 1, n as u8)
}

// 組み合わせ生成
#[allow(dead_code)]
pub fn combinations(n: usize, r: usize) -> Box<Iterator<Item = Vec<u8>>> {
    fn inner(
        array: Vec<u8>,
        depth: usize,
        min: usize,
        max: usize,
    ) -> Box<Iterator<Item = Vec<u8>>> {
        let na = (min..(max - depth)).map(move |x| {
            let mut newarray = array.clone();
            newarray.push(x as u8);
            newarray
        });
        if depth > 0 {
            Box::new(na.flat_map(move |x| {
                let next = usize::from(*x.last().unwrap()) + 1;
                inner(x, depth - 1, next, max)
            }))
        } else {
            Box::new(na)
        }
    }
    inner(Vec::new(), r - 1, 0, n)
}