#include<bits/stdc++.h>
using namespace std;
int n, t[41000000], j, a[410000000], mx[41000000], mn[41000000];
char ch;
void prn()
{
    for(int i=1;i<=4*n;i++) cout<<mn[i]<<" ";
    cout<<endl<<endl;
}

void update(int node, int start, int end, int l, int r, int add)
{
    if (l > r)
        return;
    if (l == start && r == end)
    {
        t[node] += add;
        mn[node]+=add;
        mx[node]+=add;
    }
    else
    {
        int tm = (start + end) / 2;
        update(node*2, start, tm, l, min(r, tm), add);
        update(node*2+1, tm+1, end, max(l, tm+1), r, add);
        mn[node]=min(mn[2*node], mn[2*node+1])+t[node];
        mx[node]=max(mx[2*node], mx[2*node+1])+t[node];
    }
}

int get(int node, int start, int end, int pos)
{
    if (start == end)
        return t[node];
    int tm = (start + end) / 2;
    if (pos <= tm)
        return t[node] + get(node*2, start, tm, pos);
    else
        return t[node] + get(node*2+1, tm+1, end, pos);
}

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    cin>>n;
    cin.ignore();
    j=1;
    for(int i=1; i<=n; i++)
    {
        cin>>ch;
//        cin.ignore();
        if(ch=='R')
        {
            j++;

        }

        else if(ch=='L' and j>2)
        {
            j--;
        }

        else if(ch=='(')
        {
            int flt=a[j];
            a[j]=1;
            update(1, 1, 4*n, j, 4*n, -flt+1);
        }

        else if(ch==')')
        {
            int flt=a[j];
            a[j]=-1;
            update(1, 1, 4*n, j, 4*n, -flt-1);
        }
        else
        {
            int flt=a[j];
            a[j]=0;
            update(1, 1, 4*n, j, 4*n, -flt);
        }
        int x=get(1, 1, 4*n, 4*n);
        if(mn[1]<0 or x!=0)
        {
//            cout<<ch<<" ";
//            cout<<x<<endl;
            cout<<-1<<" ";
        }
        else
            cout<<mx[1]<<" ";

    }

    return 0;
}

/**
11
(R)R(R(R)R)

3
(R)
*/


#include<bits/stdc++.h>
using namespace std;
void prn(char str[], int n)
{
	for(int i=1;i<=n;i++) cout<<str[i];
	cout<<endl<<endl;
}

int main()
{
	#ifndef ONLINE_JUDGE
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	#endif
	int n , mx=0, r=0,j=1, l=0;
	cin>>n;
	cin.ignore();
	char str[n+10];
	stack<char> spr, spst;
	for(int i=1;i<=n;i++)
	{
		// cout<<endl<<i<<" "<<mx<<" mx"<<endl;
		char ch;
		cin>>ch;
		if(ch=='R')
		{
			if(str[j]=='(') spr.push(str[j]);
			if(str[j]==')') spr.pop();
			// cout<<mx<<" bf ";
			mx=max(mx, (int)max(spr.size(), spst.size()));
			// cout<<mx<<" aft "<<endl;
		 	j++;
		}

		else if(ch=='L') {
			if(str[j]==')') spst.push(str[j]);
			if(str[j]=='(') spst.pop();

			mx=max(mx, (int)max(spr.size(), spst.size()));
			j--;
		}

		else if(ch=='(')
		{
			if(str[j]==')') r+=2;
			else if(str[j]!='(') r++;
			str[j]=ch;
		}

		else if(ch==')')
		{
			if(str[j]=='(') r-=2;
			else if(str[j]!=')') r--;
			str[j]=ch;
		}
		else {
			if(str[j]=='(') r--;
			else if(str[j]==')') r++;
			str[j]=ch;
		}

		mx=max(mx, abs(r));

		if(r==0) cout<<mx<<" ";
		else cout<<-1<<" ";
	}

	return 0;
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000010

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
int getSum(int n, int qs, int qe, int tt)
{
    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

signed main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	int v1 = 0, v2 = 0, v3 = 0;

 	string br;
 	for(int i=0;i<=n;i++) br += '*';

 	int pos = 0;

 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			pos--;
 			if(pos<0) pos = 0;

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		int f = 0;

 		if(br[pos]==s[i]) goto skip;

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else f = 1;

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else f = 1;

 		skip:

 		if(f) {
 			v1 = getSum(n,0,n-1,0);
	 		v2 = getSum(n,0,n-1,1);
	 		v3 = getSum(n,n-1,n-1,1);
 		}

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2!=0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000010

// #define max1(a,b) ((a>b)?(a):(b))

inline int max1(int a, int b) {
	return ((a>b)?(a):(b));
}

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

string br(MAX,'*');

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max1(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max1(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
inline int getSum(int n, int qs, int qe, int tt)
{
    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

int32_t main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	int v1 = 0, v2 = 0, v3 = 0;

 	int pos = 0;

 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2!=0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			if(pos) pos--;

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2!=0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		int f = 0;

 		if(br[pos]==s[i]) goto skip;

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else f++;

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else f++;

 		if(f!=2) {
	 		v2 = getSum(n,0,n-1,1);
	 		if(v2==0) v3 = getSum(n,n-1,n-1,1);
	 		if(v2==0&&v3==0) v1 = getSum(n,0,n-1,0);
 		}

 		skip:

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2!=0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000010

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
int getSum(int n, int qs, int qe, int tt)
{
    // Check for erroneous input values
    if (qs < 0 || qe > n - 1 || qs > qe) {
        printf("Invalid Input");
        return -1;
    }

    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

signed main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	for(int i=0;i<4*n+5;i++) {
 		tree[0][i] = tree[1][i] = 0;
 		lazy[0][i] = lazy[1][i] = 0;
 	}

 	string br;
 	for(int i=0;i<2*n;i++) br += '*';

 	int pos = 0;
 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			pos--;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}

 		int v1 = getSum(n,0,n-1,0);
 		int v2 = getSum(n,0,n-1,1);
 		int v3 = getSum(n,n-1,n-1,1);

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2<0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000010

#define max1(a,b) ((a>b)?(a):(b))

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max1(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max1(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
int getSum(int n, int qs, int qe, int tt)
{
    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

int32_t main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;
 	// s+='(';
 	// for(int i=0;i<n;i++) s += 'R';
 	// s+=')';

 	int v1 = 0, v2 = 0, v3 = 0;

 	string br;
 	for(int i=0;i<=n;i++) br += '*';

 	int pos = 0;

 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2!=0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			if(pos) pos--;

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2!=0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		int f = 0;

 		if(br[pos]==s[i]) goto skip;

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else f++;

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else f++;

 		if(f!=2) {
	 		v2 = getSum(n,0,n-1,1);
	 		if(v2==0) v3 = getSum(n,n-1,n-1,1);
	 		if(v2==0&&v3==0) v1 = getSum(n,0,n-1,0);
 		}

 		skip:

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2!=0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000001

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
int getSum(int n, int qs, int qe, int tt)
{
    // Check for erroneous input values
    if (qs < 0 || qe > n - 1 || qs > qe) {
        printf("Invalid Input");
        return -1;
    }

    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

signed main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	for(int i=0;i<4*n+5;i++) {
 		tree[0][i] = tree[1][i] = 0;
 		lazy[0][i] = lazy[1][i] = 0;
 	}

 	string br;
 	for(int i=0;i<2*n;i++) br += '*';

 	int pos = 0;
 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			pos--;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}

 		int v1 = getSum(n,0,n-1,0);
 		int v2 = getSum(n,0,n-1,1);
 		int v3 = getSum(n,n-1,n-1,1);

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2<0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000010

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
int getSum(int n, int qs, int qe, int tt)
{
    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

signed main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	for(int i=0;i<4*n+5;i++) {
 		tree[0][i] = tree[1][i] = 0;
 		lazy[0][i] = lazy[1][i] = 0;
 	}

 	string br;
 	for(int i=0;i<4*n;i++) br += '*';

 	int pos = 0;

 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			pos--;
 			if(pos<0) pos = 0;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}

 		int v1 = getSum(n,0,n-1,0);
 		int v2 = getSum(n,0,n-1,1);
 		int v3 = getSum(n,n-1,n-1,1);

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2!=0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;

#define MAX 4000010

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX];

// To store pending updates
int lazy[2][MAX];

string br(MAX, '*');

#define max1(a,b) ((a>b)?(a):(b))

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max1(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
inline void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max1(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
inline int getSum(int n, int qs, int qe, int tt)
{
    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

int main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	int v1 = 0, v2 = 0, v3 = 0;

 	int pos = 0;

 	for(int i=0;i<n;i++) {
 		if(s[i]=='R') {
 			pos++;

	 		if(v2!=0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			if(pos) pos--;

	 		if(v2!=0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		int f = 0;

 		if(br[pos]==s[i]) goto skip;

 		if(br[pos]=='(') {
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else f++;

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else f++;

 		if(f!=2) {
 			v1 = getSum(n,0,n-1,0);
	 		v2 = getSum(n,0,n-1,1);
	 		v3 = getSum(n,n-1,n-1,1);
 		}

		skip:

 		if(v2!=0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
#define inf 1000000000000000000LL
#define pll pair<ll,ll>
#define f first
#define s second
#define pb push_back
#define mp make_pair
#define endl '\n'
#define fast  ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
#define int ll

#define MAX 4000010

// Ideally, we should not use global variables and large
// constant-sized arrays, we have done it here for simplicity

// To store segment tree
int tree[2][MAX] = { 0 };

// To store pending updates
int lazy[2][MAX] = { 0 };

// si -> index of current node in segment tree
// ss and se -> Starting and ending indexes of
// elements for which current nodes stores sum
// us and ue -> starting and ending indexes of update query
// diff -> which we need to add in the range us to ue
void updateRangeUtil(int si, int ss, int se, int us,
                     int ue, int diff, int tt)
{
    // If lazy value is non-zero for current node of segment
    // tree, then there are some pending updates. So we need
    // to make sure that the pending updates are done before
    // making new updates. Because this value may be used by
    // parent after recursive calls (See last line of this
    // function)
    if (lazy[tt][si] != 0) {
        // Make pending updates using value stored in lazy
        // nodes
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // We can postpone updating children we don't
            // need their new values now.
            // Since we are not yet updating children of si,
            // we need to set lazy[tt] flags for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Set the lazy[tt] value for current node as 0 as it
        // has been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > ue || se < us)
        return;

    // Current segment is fully in range
    if (ss >= us && se <= ue) {
        // Add the difference to current node
        tree[tt][si] += diff;

        // Same logic for checking leaf node or not
        if (ss != se) {
            // This is where we store values in lazy[tt] nodes,
            // rather than updating the segment tree[tt] itelf
            // Since we don't need these updated values now
            // we postpone updates by storing values in lazy[tt][]
            lazy[tt][si * 2 + 1] += diff;
            lazy[tt][si * 2 + 2] += diff;
        }
        return;
    }

    // If not completely in range, but overlaps
    // recur for children
    int mid = (ss + se) / 2;
    updateRangeUtil(si * 2 + 1, ss, mid, us, ue, diff,tt);
    updateRangeUtil(si * 2 + 2, mid + 1, se, us, ue, diff,tt);

    // And use the result of children calls
    // to update this node
    tree[tt][si] = max(tree[tt][si * 2 + 1], tree[tt][si * 2 + 2]);
}

// Function to update a range of values in segment
// tree[tt]
// us and eu -> starting and ending indexes of update query
// ue -> ending index of update query
// diff -> which we need to add in the range us to ue
void updateRange(int n, int us, int ue, int diff,int tt)
{
    updateRangeUtil(0, 0, n - 1, us, ue, diff,tt);
}

// A recursive function to get the sum of values in given
// a range of the array. The following are the parameters
// for this function
// si --> Index of the current node in the segment tree[tt]
// Initially, 0 is passed as root is always at index 0
// ss & se --> Starting and ending indexes of the
// segment represented by current node
// i.e., tree[tt][si]
// qs & qe --> Starting and ending indexes of query
// range
int getSumUtil(int ss, int se, int qs, int qe, int si, int tt)
{

    // If lazy[tt] flag is set for current node of segment tree[tt]
    // then there are some pending updates. So we need to
    // make sure that the pending updates are done before
    // processing the sub sum query
    if (lazy[tt][si] != 0) {

        // Make pending updates to this node. Note that this
        // node represents sum of elements in arr[ss..se] and
        // all these elements must be increased by lazy[tt][si]
        tree[tt][si] += lazy[tt][si];

        // Checking if it is not leaf node because if
        // it is leaf node then we cannot go further
        if (ss != se) {
            // Since we are not yet updating children os si,
            // we need to set lazy[tt] values for the children
            lazy[tt][si * 2 + 1] += lazy[tt][si];
            lazy[tt][si * 2 + 2] += lazy[tt][si];
        }

        // Unset the lazy[tt] value for current node as it has
        // been updated
        lazy[tt][si] = 0;
    }

    // Out of range
    if (ss > se || ss > qe || se < qs)
        return -inf;

    // At this point, we are sure that pending lazy[tt] updates
    // are done for current node. So we can return value
    // (same as it was for a query in our previous post)

    // If this segment lies in range
    if (ss >= qs && se <= qe)
        return tree[tt][si];

    // If a part of this segment overlaps with the given
    // range
    int mid = (ss + se) / 2;
    return max(getSumUtil(ss, mid, qs, qe, 2 * si + 1,tt),
               getSumUtil(mid + 1, se, qs, qe, 2 * si + 2,tt));
}

// Return sum of elements in range from index qs (query
// start) to qe (query end). It mainly uses getSumUtil()
int getSum(int n, int qs, int qe, int tt)
{
	    // Check for erroneous input values
	    if (qs < 0 || qe > n - 1 || qs > qe) {
	        assert(0);
	    }

    return getSumUtil(0, n - 1, qs, qe, 0, tt);
}

signed main() {
	fast;

 	int n;
 	cin>>n;

 	string s;
 	cin>>s;

 	for(int i=0;i<4*n+5;i++) {
 		tree[0][i] = tree[1][i] = 0;
 		lazy[0][i] = lazy[1][i] = 0;
 	}

 	string br;
 	for(int i=0;i<4*n;i++) br += '*';

 	int nn = n;
 n = n+100;

 	int pos = 0;
 	for(int i=0;i<nn;i++) {
 		if(s[i]=='R') {
 			pos++;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}
 		if(s[i]=='L') {
 			pos--;
 			if(pos<0) pos = 0;
 			int v1 = getSum(n,0,n-1,0);
	 		int v2 = getSum(n,0,n-1,1);
	 		int v3 = getSum(n,n-1,n-1,1);

	 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<endl;

	 		if(v2<0||v3!=0) cout<<-1<<" ";
	 		else cout<<v1<<" ";
 			continue;
 		}

 		if(br[pos]=='(') {
 			// cout<<"$"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}
 		else if(br[pos]==')') {
 			// cout<<"$$"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}

 		br[pos] = s[i];

 		if(s[i]=='(') {
 			// cout<<"#"<<i<<endl;
 			updateRange(n, pos, n-1, 1,0);
 			updateRange(n, pos, n-1, -1,1);
 		}
 		else if(s[i]==')') {
 			// cout<<"##"<<i<<endl;
 			updateRange(n, pos, n-1, -1,0);
 			updateRange(n, pos, n-1, 1,1);
 		}

 		int v1 = getSum(n,0,n-1,0);
 		int v2 = getSum(n,0,n-1,1);
 		int v3 = getSum(n,n-1,n-1,1);

 		// cout<<"$$$"<<i<<" -- "<<v1<<" "<<v2<<" "<<v3<<endl;

 		if(v2!=0||v3!=0) cout<<-1<<" ";
 		else cout<<v1<<" ";
 	}
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	if (lazy[idx] == 0) return;

	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;
	cin.ignore();

	string s;
	getline(cin, s);

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			if (f[pos] != 1) {
				update(pos, SZ - 1, 1 + (f[pos] == -1));
				f[pos] = 1;
			}
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			if (f[pos] != -1) {
				update(pos, SZ - 1, -1 - (f[pos] == 1));
				f[pos] = -1;
			}
		}
		else if (f[pos] == 1) {
			update(pos, SZ - 1, -1);
			f[pos] = 0;
		}
		else if (f[pos] == -1) {
			update(pos, SZ - 1, 1);
			f[pos] = 0;
		}

		if (query(SZ - 1, SZ - 1).X != 0 || query(0, SZ - 1).Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;

	string s;
	cin >> s;

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			update(pos, SZ - 1, 1);
			f[pos] = 1;
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			update(pos, SZ - 1, -1);
			f[pos] = -1;
		}
		else if (f[pos] == 1) update(pos, SZ - 1, -1);
		else if (f[pos] == -1) update(pos, SZ - 1, 1);

		pii q = query(SZ - 1, SZ - 1);
		if (q.X != 0 || q.Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;

	string s;
	cin >> s;

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			if (f[pos] != 1) {
				update(pos, SZ - 1, 1 + (f[pos] == -1));
				f[pos] = 1;
			}
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			if (f[pos] != -1) {
				update(pos, SZ - 1, -1 - (f[pos] == 1));
				f[pos] = -1;
			}
		}
		else if (f[pos] == 1) {
			update(pos, SZ - 1, -1);
			f[pos] = 0;
		}
		else if (f[pos] == -1) {
			update(pos, SZ - 1, 1);
			f[pos] = 0;
		}

		pii q = query(SZ - 1, SZ - 1);
		if (q.X != 0 || q.Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;
	cin.ignore();

	string s;
	getline(cin, s);

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			if (f[pos] != 1) {
				update(pos, SZ - 1, 1 + (f[pos] == -1));
				f[pos] = 1;
			}
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			if (f[pos] != -1) {
				update(pos, SZ - 1, -1 - (f[pos] == 1));
				f[pos] = -1;
			}
		}
		else if (f[pos] == 1) {
			update(pos, SZ - 1, -1);
			f[pos] = 0;
		}
		else if (f[pos] == -1) {
			update(pos, SZ - 1, 1);
			f[pos] = 0;
		}

		if (query(SZ - 1, SZ - 1).X != 0 || query(0, SZ - 1).Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;

	string s;
	getline(cin, s);

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			if (f[pos] != 1) {
				update(pos, SZ - 1, 1 + (f[pos] == -1));
				f[pos] = 1;
			}
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			if (f[pos] != -1) {
				update(pos, SZ - 1, -1 - (f[pos] == 1));
				f[pos] = -1;
			}
		}
		else if (f[pos] == 1) {
			update(pos, SZ - 1, -1);
			f[pos] = 0;
		}
		else if (f[pos] == -1) {
			update(pos, SZ - 1, 1);
			f[pos] = 0;
		}

		pii q = query(SZ - 1, SZ - 1);
		if (q.X != 0 || q.Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;

	string s;
	cin >> s;

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			update(pos, SZ - 1, 1);
			f[pos] = 1;
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			update(pos, SZ - 1, -1);
			f[pos] = -1;
		}
		else if (f[pos] == 1) {
			update(pos, SZ - 1, -1);
			f[pos] = 0;
		}
		else if (f[pos] == -1) {
			update(pos, SZ - 1, 1);
			f[pos] = 0;
		}

		pii q = query(SZ - 1, SZ - 1);
		if (q.X != 0 || q.Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


#include <bits/stdc++.h>
#define X first
#define Y second
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
#define ini(x, y) memset(x, y, sizeof(x))
#define endl '\n'
#define fastio cin.sync_with_stdio(false); cin.tie(nullptr)
using namespace std;

using ll = long long;
using pii = pair<int, int>;
const int MOD = 1e9 + 7;
const int dx[] = { -1, 0, 1, 0, -1, 1, 1, -1 };
const int dy[] = { 0, 1, 0, -1, 1, 1, -1, -1 };

const int SZ = 1 << 20;
pii tree[SZ << 1];
int lazy[SZ << 1];

void prop(int idx, int s, int e) {
	tree[idx].X += lazy[idx];
	tree[idx].Y += lazy[idx];
	if (s != e) {
		lazy[idx << 1] += lazy[idx];
		lazy[idx << 1 | 1] += lazy[idx];
	}
	lazy[idx] = 0;
}
void update(int L, int R, int val, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return;
	if (L <= s && e <= R) {
		lazy[idx] += val;
		prop(idx, s, e);
		return;
	}

	int m = s + e >> 1;
	update(L, R, val, idx << 1, s, m);
	update(L, R, val, idx << 1 | 1, m + 1, e);
	tree[idx].X = max(tree[idx << 1].X, tree[idx << 1 | 1].X);
	tree[idx].Y = min(tree[idx << 1].Y, tree[idx << 1 | 1].Y);
}
pii query(int L, int R, int idx = 1, int s = 0, int e = SZ - 1) {
	prop(idx, s, e);
	if (e < s || e < L || R < s) return { 0, 0 };
	if (L <= s && e <= R) return tree[idx];

	int m = s + e >> 1;
	pii retL = query(L, R, idx << 1, s, m);
	pii retR = query(L, R, idx << 1 | 1, m + 1, e);

	return { max(retL.X, retR.X), min(retL.Y, retR.Y) };
}
int main() {
	fastio;
	int n;
	cin >> n;
	cin.ignore();

	string s;
	getline(cin, s);

	int pos = 0;
	int f[SZ]{};
	for (char ch : s) {
		if (ch == '(') {
			if (f[pos] != 1) {
				update(pos, SZ - 1, 1 + (f[pos] == -1));
				f[pos] = 1;
			}
		}
		else if (ch == 'L') pos = max(pos - 1, 0);
		else if (ch == 'R') ++pos;
		else if (ch == ')') {
			if (f[pos] != -1) {
				update(pos, SZ - 1, -1 - (f[pos] == 1));
				f[pos] = -1;
			}
		}
		else if (f[pos] == 1) {
			update(pos, SZ - 1, -1);
			f[pos] = 0;
		}
		else if (f[pos] == -1) {
			update(pos, SZ - 1, 1);
			f[pos] = 0;
		}

		pii q = query(SZ - 1, SZ - 1);
		if (q.X != 0 || q.Y != 0) cout << -1 << ' ';
		else cout << query(0, SZ - 1).X << ' ';
	}

	return 0;
}


//C.cpp
#include<bits/stdc++.h>
using namespace std;
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/tree_policy.hpp>
//
//    #pragma GCC optimize("-O3")
////    #pragma GCC optimize("no-stack-protector")
//    #pragma GCC optimize("fast-math")
//    #pragma GCC optimize("Ofast")
//    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx")
//    #pragma GCC target("avx,avx2,fma")
//    #pragma GCC optimization ("unroll-loops")
//#define LOCAL
#define sim template < class c
#define ris return * this
#define dor > debug & operator <<
#define eni(x) sim > typename \
  enable_if<sizeof dud<c>(0) x 1, debug&>::type operator<<(c i) {
sim > struct rge { c b, e; };
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifndef LOCAL
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
	*this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
sim dor(const c&) { ris; }
#endif
};
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define fi first
#define f first
#define se second
#define s second
#define vi_a vector<int>a;
#define p_b push_back
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ll long long
typedef unsigned long long ull;
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ld long double
#define pll pair<ll,ll>
#define pii pair<int,int>
#define m_p make_pair
#define fast_io cin.tie(0);cout.tie(0);ios_base::sync_with_stdio(0);
#define all(x) x.begin(),x.end()
#define getfiles    ifstream cin("input.txt");ofstream cout("output.txt");
#define pw(x) (1ll << x)
#define sz(x) (int)x.size()
#define endl "\n"
#define rall(x) x.rbegin(),x.rend()
#define len(a) (ll)a.size()
#define rep(x,l,r) for(ll x=l;x<r;x++)

//using namespace __gnu_pbds;
ld eps = (ld)1 / 1e6;
const ld pi=3.14159265359;
ll inf = 1e18,mod1=1e9+7;
ll sqr(ll a) { return a * a; }
ll qb(ll a) { return a * a * a; }
template<typename T> bool umax(T& a, T b) {return a<b?a=b,1:0;}
template<typename T> bool umin(T& a, T b) {return b<a?a=b,1:0;}
bool is_prime(ll val){for(ll i=2;i<=sqrt(val);i++)if(val%i==0)return 0; return 1;}
ll gcd(ll a, ll b) { return !a ? b : gcd(b % a, a); }
ll binpow(ll a, ll b, ll mod) { return b ? (b % 2 ? (a * (sqr(binpow(a, b / 2, mod)) % mod)) % mod : sqr(binpow(a, b / 2, mod)) % mod) : 1; }ll binmult(ll a, ll b, ll mod) { return b ? (b % 2 ? (2 * binmult(a, b / 2, mod) + a) % mod : (2 * binmult(a, b / 2, mod)) % mod) : 0; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const ll RR=1e4;
const ll tx[8]={1,-1,2,-2,-1,-2};
const ll ty[8]={-2,-2,-1,-1,2,1};
const char rev_to[4]={'E','W','N','S'};
const int M=1e9+7;
const int N=1e6+2;
const int ppp=73;
const int pr=3;
const int block=320;
const int OPEN=1;
const int CLOSE=0;
typedef pair<long long,int> pli;
//typedef tree<pli,null_type,less<pli>,rb_tree_tag,tree_order_statistics_node_update> o_st;
auto rnd=bind(uniform_int_distribution<ll>(1,125),mt19937(time(0)));
void bad(){
    cout<<"-1";
    exit(0);
}
struct fnw{
    int f[N];
    void init(){
        fill(f,f+N,0);
    }
    void upd(int pos,int val){
        while(pos<N){
            f[pos]+=val;
            pos+=pos&-pos;
        }
    }
    int get(int pos){
        int val=0;
        while(pos>0){
            val+=f[pos];
            pos-=pos&-pos;
        }
        return val;
    }
}a;
struct DO{
    int tp;
    int tree[4*N];
    int ex(int type){
        int to;
        if(tp==2) to=2e9;
        else if(tp==3) to=-2e9;
        return to;
    }
    void init(int type){
        tp=type;
        fill(tree,tree+4*N,ex(tp));
    }
    int pull(int a,int b){
        if(tp==1) return a+b;
        else if(tp==2) return min(a,b);
        else return max(a,b);
    }
    void upd(int pos,int val,int v,int tl,int tr){
        if(tl==tr){
            tree[v]=val;return;
        }
        int tm=(tl+tr)>>1;
        if(tm>=pos)upd(pos,val,2*v,tl,tm);
        else upd(pos,val,2*v+1,tm+1,tr);
        tree[v]=pull(tree[2*v],tree[2*v+1]);
    }
}b,c;
int kek[N];
signed main()
{
    fast_io;
    int n;
    cin>>n;
    a.init();
    b.init(2);
    c.init(3);
    int pos=1;
    string s;
    cin>>s;
    for(auto &z : s){
        if(z=='R')pos++;
        else if(z=='L') pos--;
        else{
            int w;
            int pr=kek[pos];
            if(z=='(') w=1;
            else if(z==')') w=-1;
            else w=0;
            a.upd(pos,w-pr);
            int nw=a.get(pos);
            kek[pos]=w;
//            debug()<<imie(a.get(pos));
            b.upd(pos,nw,1,0,N-1);
            c.upd(pos,nw,1,0,N-1);
        }
        if(b.tree[1]==0){
            if(a.get(N-1)==0){
                cout<<c.tree[1];
            }else cout<<-1;
        }else cout<<-1;
        cout<<' ';
//        debug()<<imie(pos);
    }
    return 0;
}
/*
3 3
001
010fsdfs
110dadadasd
*/


//C.cpp
#include<bits/stdc++.h>
using namespace std;
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/tree_policy.hpp>
//
//    #pragma GCC optimize("-O3")
////    #pragma GCC optimize("no-stack-protector")
//    #pragma GCC optimize("fast-math")
//    #pragma GCC optimize("Ofast")
//    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx")
//    #pragma GCC target("avx,avx2,fma")
//    #pragma GCC optimization ("unroll-loops")
//#define LOCAL
#define sim template < class c
#define ris return * this
#define dor > debug & operator <<
#define eni(x) sim > typename \
  enable_if<sizeof dud<c>(0) x 1, debug&>::type operator<<(c i) {
sim > struct rge { c b, e; };
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifndef LOCAL
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
	*this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
sim dor(const c&) { ris; }
#endif
};
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define fi first
#define f first
#define se second
#define s second
#define vi_a vector<int>a;
#define p_b push_back
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ll long long
typedef unsigned long long ull;
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ld long double
#define pll pair<ll,ll>
#define pii pair<int,int>
#define m_p make_pair
#define fast_io cin.tie(0);cout.tie(0);ios_base::sync_with_stdio(0);
#define all(x) x.begin(),x.end()
#define getfiles    ifstream cin("input.txt");ofstream cout("output.txt");
#define pw(x) (1ll << x)
#define sz(x) (int)x.size()
#define endl "\n"
#define rall(x) x.rbegin(),x.rend()
#define len(a) (ll)a.size()
#define rep(x,l,r) for(ll x=l;x<r;x++)

//using namespace __gnu_pbds;
ld eps = (ld)1 / 1e6;
const ld pi=3.14159265359;
ll inf = 1e18,mod1=1e9+7;
ll sqr(ll a) { return a * a; }
ll qb(ll a) { return a * a * a; }
template<typename T> bool umax(T& a, T b) {return a<b?a=b,1:0;}
template<typename T> bool umin(T& a, T b) {return b<a?a=b,1:0;}
bool is_prime(ll val){for(ll i=2;i<=sqrt(val);i++)if(val%i==0)return 0; return 1;}
ll gcd(ll a, ll b) { return !a ? b : gcd(b % a, a); }
ll binpow(ll a, ll b, ll mod) { return b ? (b % 2 ? (a * (sqr(binpow(a, b / 2, mod)) % mod)) % mod : sqr(binpow(a, b / 2, mod)) % mod) : 1; }ll binmult(ll a, ll b, ll mod) { return b ? (b % 2 ? (2 * binmult(a, b / 2, mod) + a) % mod : (2 * binmult(a, b / 2, mod)) % mod) : 0; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const ll RR=1e4;
const ll tx[8]={1,-1,2,-2,-1,-2};
const ll ty[8]={-2,-2,-1,-1,2,1};
const char rev_to[4]={'E','W','N','S'};
const int M=1e9+7;
const int N=1e6+2;
const int ppp=73;
const int pr=3;
const int block=320;
const int OPEN=1;
const int CLOSE=0;
typedef pair<long long,int> pli;
//typedef tree<pli,null_type,less<pli>,rb_tree_tag,tree_order_statistics_node_update> o_st;
auto rnd=bind(uniform_int_distribution<ll>(1,125),mt19937(time(0)));
void bad(){
    cout<<"-1";
    exit(0);
}
struct fnw{
    int f[N];
    void init(){
        fill(f,f+N,0);
    }
    void upd(int pos,int val){
        while(pos<N){
            f[pos]+=val;
            pos+=pos&-pos;
        }
    }
    int get(int pos){
        int val=0;
        while(pos>0){
            val+=f[pos];
            pos-=pos&-pos;
        }
        return val;
    }
}a;
struct DO{
    int tp;
    int tree[4*N];
    int ex(int type){
        int to;
        if(tp==2) to=2e9;
        else if(tp==3) to=-2e9;
        return to;
    }
    void init(int type){
        tp=type;
        fill(tree,tree+4*N,ex(tp));
    }
    int pull(int a,int b){
        if(tp==1) return a+b;
        else if(tp==2) return min(a,b);
        else return max(a,b);
    }
    void upd(int pos,int val,int v,int tl,int tr){
        if(tl==tr){
            tree[v]=val;return;
        }
        int tm=(tl+tr)>>1;
        if(tm>=pos)upd(pos,val,2*v,tl,tm);
        else upd(pos,val,2*v+1,tm+1,tr);
        tree[v]=pull(tree[2*v],tree[2*v+1]);
    }
}b,c;
int kek[N];
signed main()
{
    fast_io;
    int n;
    cin>>n;
    a.init();
    b.init(2);
    c.init(3);
    int pos=1;
    string s;
    cin>>s;
    for(auto &z : s){
        if(z=='R')pos++;
        else if(z=='L') pos--;
        else{
            int w;
            int pr=kek[pos];
            if(z=='(') w=1;
            else if(z==')') w=-1;
            else w=0;
            a.upd(pos,w-pr);
            int nw=a.get(pos);
            kek[pos]=nw;
//            debug()<<imie(a.get(pos));
            b.upd(pos,nw,1,0,N-1);
            c.upd(pos,nw,1,0,N-1);
        }
        if(b.tree[1]==0){
            if(a.get(N-1)==0){
                cout<<c.tree[1];
            }else cout<<-1;
        }else cout<<-1;
        cout<<' ';
//        debug()<<imie(pos);
    }
    return 0;
}
/*
3 3
001
010fsdfs
110dadadasd
*/


//C.cpp
#include<bits/stdc++.h>
using namespace std;
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/tree_policy.hpp>
//
//    #pragma GCC optimize("-O3")
////    #pragma GCC optimize("no-stack-protector")
//    #pragma GCC optimize("fast-math")
//    #pragma GCC optimize("Ofast")
//    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx")
//    #pragma GCC target("avx,avx2,fma")
//    #pragma GCC optimization ("unroll-loops")
//#define LOCAL
#define sim template < class c
#define ris return * this
#define dor > debug & operator <<
#define eni(x) sim > typename \
  enable_if<sizeof dud<c>(0) x 1, debug&>::type operator<<(c i) {
sim > struct rge { c b, e; };
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifndef LOCAL
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
	*this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
sim dor(const c&) { ris; }
#endif
};
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define fi first
#define f first
#define se second
#define s second
#define vi_a vector<int>a;
#define p_b push_back
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ll long long
typedef unsigned long long ull;
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ld long double
#define pll pair<ll,ll>
#define pii pair<int,int>
#define m_p make_pair
#define fast_io cin.tie(0);cout.tie(0);ios_base::sync_with_stdio(0);
#define all(x) x.begin(),x.end()
#define getfiles    ifstream cin("input.txt");ofstream cout("output.txt");
#define pw(x) (1ll << x)
#define sz(x) (int)x.size()
#define endl "\n"
#define rall(x) x.rbegin(),x.rend()
#define len(a) (ll)a.size()
#define rep(x,l,r) for(ll x=l;x<r;x++)

//using namespace __gnu_pbds;
ld eps = (ld)1 / 1e6;
const ld pi=3.14159265359;
ll inf = 1e18,mod1=1e9+7;
ll sqr(ll a) { return a * a; }
ll qb(ll a) { return a * a * a; }
template<typename T> bool umax(T& a, T b) {return a<b?a=b,1:0;}
template<typename T> bool umin(T& a, T b) {return b<a?a=b,1:0;}
bool is_prime(ll val){for(ll i=2;i<=sqrt(val);i++)if(val%i==0)return 0; return 1;}
ll gcd(ll a, ll b) { return !a ? b : gcd(b % a, a); }
ll binpow(ll a, ll b, ll mod) { return b ? (b % 2 ? (a * (sqr(binpow(a, b / 2, mod)) % mod)) % mod : sqr(binpow(a, b / 2, mod)) % mod) : 1; }ll binmult(ll a, ll b, ll mod) { return b ? (b % 2 ? (2 * binmult(a, b / 2, mod) + a) % mod : (2 * binmult(a, b / 2, mod)) % mod) : 0; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const ll RR=1e4;
const ll tx[8]={1,-1,2,-2,-1,-2};
const ll ty[8]={-2,-2,-1,-1,2,1};
const char rev_to[4]={'E','W','N','S'};
const int M=1e9+7;
const int N=1e6+2;
const int ppp=73;
const int pr=3;
const int block=320;
const int OPEN=1;
const int CLOSE=0;
typedef pair<long long,int> pli;
//typedef tree<pli,null_type,less<pli>,rb_tree_tag,tree_order_statistics_node_update> o_st;
auto rnd=bind(uniform_int_distribution<ll>(1,125),mt19937(time(0)));
void bad(){
    cout<<"-1";
    exit(0);
}
pii t[4*N];
int la[4*N];
void push(int v,int tl,int tr){
    if(la[v]){
        la[2*v]+=la[v];
        la[2*v+1]+=la[v];
        t[2*v].f+=la[v];
        t[2*v+1].f+=la[v];
        t[2*v].s+=la[v];
        t[2*v+1].s+=la[v];
        la[v]=0;
    }
}
void upd(int l,int r,int val,int v,int tl,int tr){
    if(tr<l || tl>r) return;
    if(tl>=l&&tr<=r){
        t[v].f+=val;
        t[v].s+=val;
        la[v]+=val;
        return ;
    }
    push(v,tl,tr);
    int tm=(tl+tr)>>1;
    upd(l,r,val,2*v,tl,tm);
    upd(l,r,val,2*v+1,tm+1,tr);
    t[v].f=min(t[2*v].f,t[2*v+1].f);
    t[v].s=max(t[2*v].s,t[2*v+1].s);
//    debug()<<imie(t[v]);
}
int kek[N];
signed main()
{
    fast_io;
//    int n;
//    cin>>n;
    int pos=1;
    string s;
    cin>>s;
    int k=0;
    for(auto &z : s){
        if(z=='R')pos++;
        else if(z=='L') pos--;
        else{
            int w;
            int pr=kek[pos];
            if(z=='(') w=1;
            else if(z==')') w=-1;
            else w=0;
            upd(pos,N-1,w-pr,1,0,N-1);
            kek[pos]=w;
            k+=w-pr;
        }
        if(t[1].f==0){
            if(k==0){
                cout<<t[1].s;
            }else cout<<-1;
        }else cout<<-1;
        cout<<' ';
//        debug()<<imie(t[1]);
//        debug()<<imie(pos);
    }
    return 0;
}
/*
3 3
001
010fsdfs
110dadadasd
*/


//C.cpp
#include<bits/stdc++.h>
using namespace std;
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/tree_policy.hpp>
//
//    #pragma GCC optimize("-O3")
////    #pragma GCC optimize("no-stack-protector")
//    #pragma GCC optimize("fast-math")
//    #pragma GCC optimize("Ofast")
//    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx")
//    #pragma GCC target("avx,avx2,fma")
//    #pragma GCC optimization ("unroll-loops")
//#define LOCAL
#define sim template < class c
#define ris return * this
#define dor > debug & operator <<
#define eni(x) sim > typename \
  enable_if<sizeof dud<c>(0) x 1, debug&>::type operator<<(c i) {
sim > struct rge { c b, e; };
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifndef LOCAL
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
	*this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
sim dor(const c&) { ris; }
#endif
};
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define fi first
#define f first
#define se second
#define s second
#define vi_a vector<int>a;
#define p_b push_back
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ll long long
typedef unsigned long long ull;
////////////////////////////////???????????????CHECK THIS OUT???????????????//////////////////////////////
#define ld long double
#define pll pair<ll,ll>
#define pii pair<int,int>
#define m_p make_pair
#define fast_io cin.tie(0);cout.tie(0);ios_base::sync_with_stdio(0);
#define all(x) x.begin(),x.end()
#define getfiles    ifstream cin("input.txt");ofstream cout("output.txt");
#define pw(x) (1ll << x)
#define sz(x) (int)x.size()
#define endl "\n"
#define rall(x) x.rbegin(),x.rend()
#define len(a) (ll)a.size()
#define rep(x,l,r) for(ll x=l;x<r;x++)

//using namespace __gnu_pbds;
ld eps = (ld)1 / 1e6;
const ld pi=3.14159265359;
ll inf = 1e18,mod1=1e9+7;
ll sqr(ll a) { return a * a; }
ll qb(ll a) { return a * a * a; }
template<typename T> bool umax(T& a, T b) {return a<b?a=b,1:0;}
template<typename T> bool umin(T& a, T b) {return b<a?a=b,1:0;}
bool is_prime(ll val){for(ll i=2;i<=sqrt(val);i++)if(val%i==0)return 0; return 1;}
ll gcd(ll a, ll b) { return !a ? b : gcd(b % a, a); }
ll binpow(ll a, ll b, ll mod) { return b ? (b % 2 ? (a * (sqr(binpow(a, b / 2, mod)) % mod)) % mod : sqr(binpow(a, b / 2, mod)) % mod) : 1; }ll binmult(ll a, ll b, ll mod) { return b ? (b % 2 ? (2 * binmult(a, b / 2, mod) + a) % mod : (2 * binmult(a, b / 2, mod)) % mod) : 0; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const ll RR=1e4;
const ll tx[8]={1,-1,2,-2,-1,-2};
const ll ty[8]={-2,-2,-1,-1,2,1};
const char rev_to[4]={'E','W','N','S'};
const int M=1e9+7;
const int N=1e6+2;
const int ppp=73;
const int pr=3;
const int block=320;
const int OPEN=1;
const int CLOSE=0;
typedef pair<long long,int> pli;
//typedef tree<pli,null_type,less<pli>,rb_tree_tag,tree_order_statistics_node_update> o_st;
auto rnd=bind(uniform_int_distribution<ll>(1,125),mt19937(time(0)));
void bad(){
    cout<<"-1";
    exit(0);
}
struct fnw{
    int f[N];
    void init(){
        fill(f,f+N,0);
    }
    void upd(int pos,int val){
        while(pos<N){
            f[pos]+=val;
            pos+=pos&-pos;
        }
    }
    int get(int pos){
        int val=0;
        while(pos>0){
            val+=f[pos];
            pos-=pos&-pos;
        }
        return val;
    }
}a;
struct DO{
    int tp;
    int tree[4*N];
    int ex(int type){
        int to;
        if(tp==2) to=2e9;
        else if(tp==3) to=-2e9;
        return to;
    }
    void init(int type){
        tp=type;
        fill(tree,tree+4*N,ex(tp));
    }
    int pull(int a,int b){
        if(tp==1) return a+b;
        else if(tp==2) return min(a,b);
        else return max(a,b);
    }
    void upd(int pos,int val,int v,int tl,int tr){
        if(tl==tr){
            tree[v]=val;return;
        }
        int tm=(tl+tr)>>1;
        if(tm>=pos)upd(pos,val,2*v,tl,tm);
        else upd(pos,val,2*v+1,tm+1,tr);
        tree[v]=pull(tree[2*v],tree[2*v+1]);
    }
}b,c;
int kek[N];
signed main()
{
    fast_io;
    int n;
    cin>>n;
    a.init();
    b.init(2);
    c.init(3);
    int pos=1;
    string s;
    cin>>s;
    for(auto &z : s){
        if(z=='R')pos++;
        else if(z=='L') pos--;
        else{
            int w;
            int pr=kek[pos];
            if(z=='(') w=1;
            else if(z==')') w=-1;
            else w=0;
            a.upd(pos,w-pr);
            int nw=a.get(pos);
            kek[pos]=nw;
//            debug()<<imie(a.get(pos));
            b.upd(pos,nw,1,0,N-1);
            c.upd(pos,nw,1,0,N-1);
        }
        if(b.tree[1]==0){
            if(a.get(N-1)==0){
                cout<<c.tree[1];
            }else cout<<-1;
        }else cout<<-1;
        cout<<' ';
//        debug()<<imie(pos);
    }
    return 0;
}
/*
3 3
001
010
110dadadasd
*/