use std::io::{self, *};

fn main() {
    let stdin = io::stdin();
    unsafe {
        for line in stdin.lock().lines() {
            let ll = line.unwrap();
            let numbers: Vec<&str> = ll.split(" ").collect();
            let ans = min_add(numbers[0]);
            print!("{}\n", ans);
        }
    }
}

fn min_add2(s: &str) -> i32 {
    let sc: Vec<char> = s.chars().collect();
    let mut stack: Vec<char> = vec![];
    let mut ans = 0;
    for c in sc.iter() {
        if *c == ']' || *c == ')' {
            loop {
                if stack.len() == 0 {
                    ans += 1;
                    break;
                }
                if *c == ']' {
                    if stack[stack.len() - 1] == '[' {
                        stack.pop();
                        break;
                    }
                    ans += 1;
                    stack.pop();
                } else if *c == ')' {
                    if stack[stack.len() - 1] == '(' {
                        stack.pop();
                        break;
                    }
                    ans += 1;
                    stack.pop();
                }
            }
        } else if *c == '[' || *c == '(' {
            stack.push(*c);
        }
    }
    ans
}

fn min_add(s: &str) -> i32 {
    let mut n = s.len() as i32;
    let mut dp: Vec<Vec<i32>> = vec![];
    for i in 0..n {
        dp.push(vec![]);
        for _ in 0..n {
            dp[i as usize].push(-1);
        }
    }
    return process(s, 0, s.len() as i32 - 1, &mut dp);
}

// 让s[l...r]都完美匹配
// 至少需要加几个字符
fn process(s: &str, l: i32, r: i32, dp: &mut Vec<Vec<i32>>) -> i32 {
    // 只有一个字符,不管是什么,要想配对,都需要添加一个字符
    if l == r {
        return 1;
    }
    // 只有两个字符,
    // 如果是()、[],那什么也不需要添加
    // 否则,都需要添加2个字符
    let sc: Vec<char> = s.chars().collect();
    if l == r - 1 {
        if (sc[l as usize] == '(' && sc[r as usize] == ')')
            || (sc[l as usize] == '[' && sc[r as usize] == ']')
        {
            return 0;
        }
        return 2;
    }
    if dp[l as usize][r as usize] != -1 {
        return dp[l as usize][r as usize];
    }
    // 重点是如下的过程
    // 可能性1,先搞定l+1...r,然后搞定l
    // 比如s[l...r] = ([][]
    // 先搞定[][],需要添加0个,然后搞定(,需要添加1个
    // 整体变成([][])搞定
    // l....r -> l l+1....r ?
    let p1 = 1 + process(s, l + 1, r, dp);
    // 可能性2,先搞定l...r-1,然后搞定r
    // 和可能性1同理
    // l...r -> ? l...r-1 r
    let p2 = 1 + process(s, l, r - 1, dp);
    // l( ...r) l+1..r-1
    // l[    r] l+1..r-1
    // 可能性3,s[l]和s[r]天然匹配,需要搞定的就是l+1..r-1
    // 比如([[),搞定中间的[[,就是最优解了
    let mut p3 = i32::MAX;
    if (sc[l as usize] == '(' && sc[r as usize] == ')')
        || (sc[l as usize] == '[' && sc[r as usize] == ']')
    {
        p3 = process(s, l + 1, r - 1, dp);
    }

    // l......r
    // l..l   l+1..r
    // l..l+1 l+2..r
    // l...l+2 l+3..r
    // 可能性后续:可能,最优解并不是l....r整体变成最大的嵌套
    // 而是,并列关系!
    // l....split 先变成合法
    // split+1...r 再变成合法
    // 是并列的关系!
    // 比如(())[[]]
    // l...split : (())
    // split+1...r : [[]]
    // 这种并列关系下,有可能出最优解
    // 所以,枚举每一个可能的并列划分点(split)
    let mut ans = get_min(p1, get_min(p2, p3));
    for split in l..r {
        ans = get_min(ans, process(s, l, split, dp) + process(s, split + 1, r, dp));
    }
    dp[l as usize][r as usize] = ans;
    return ans;
}

fn get_min<T: Clone + Copy + std::cmp::PartialOrd>(a: T, b: T) -> T {
    if a < b {
        a
    } else {
        b
    }
}

经过测试用栈是不行的