// 并列,嵌套两种方法
// 思路s[l...r]范围内至少插入多少字符能让它变正确
// 两头端点的讨论和划分点的讨论揉在一起的题
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e5+10;
const int MOD=1e9+7;
const int INF=0x3f3f3f3f3f3f3f3f;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};

int f2(string& s,int l,int r,vector<vector<int>>& f){
    // base case:
    // l==r,不管剩哪一个字符都缺一个
    if(l==r){
        return 1;
    }
    // +这个base case 之后下面的边界就会少扣一些
    if(l+1==r){
        if((s[l]=='('&&s[r]==')')||(s[l]=='['&&s[r]==']')){
            return 0;
        }
        return 2;
    }
    // 正常情况:
    // [l...r]范围字符数量大于等于3
    if(f[l][r]!=-1){
        return f[l][r];
    }
    // 两种可能性展开
    int p1=INT_MAX;
    // l位置和r位置可以自我消化(本来就是配对的)
    if((s[l]=='('&&s[r]==')')||(s[l]=='['&&s[r]==']')){
        p1=f2(s,l+1,r-1,f);//那就看看中间范围上要插入几个
    }
    int p2=INT_MAX;
    // 基于每个划分点做可能性划分
    for(int k=l;k<r;k++){
        // l...k k+1...r
        // 来一个k就计算左侧需要几个,右侧需要几个加起来
        // 枚举所有可能的k,那其中划分这种可能的最小值抓出来
        p2=min(p2,f2(s,l,k,f)+f2(s,k+1,r,f));
    }
    int ans=min(p1,p2);
    f[l][r]=ans;
    return ans;
}
void sol(){
    string s;cin>>s;
    int n=s.size();
    vector<vector<int>> f(n,vector<int>(n,-1));
    cout<<f2(s,0,s.size()-1,f);
}
signed main(){
    int T;T=1;
    // cin>>T;
    while(T--){
        sol();
    }
    return 0;
}