#include using namespace std; using u64 = uint64_t; struct StateKey { u64 h1; u64 h2; bool operator==(const StateKey& other) const { return h1 == other.h1 && h2 == other.h2; } }; struct StateKeyHash { size_t operator()(const StateKey& key) const { u64 x = key.h1 ^ (key.h2 + 0x9e3779b97f4a7c15ULL + (key.h1 << 6) + (key.h1 >> 2)); x ^= x >> 30; x *= 0xbf58476d1ce4e5b9ULL; x ^= x >> 27; x *= 0x94d049bb133111ebULL; x ^= x >> 31; return static_cast(x); } }; struct Operation { int type; int x; int y; }; struct Query { int left; int right; int id; }; struct Group { int representativeTime = -1; vector queries; }; class FenwickTree { private: int n; vector tree; public: explicit FenwickTree(int size) : n(size), tree(size + 1, 0) {} void add(int position, int value) { for (int i = position; i <= n; i += i & -i) { tree[i] += value; } } int prefixSum(int position) const { int result = 0; for (int i = position; i > 0; i -= i & -i) { result += tree[i]; } return result; } int rangeSum(int left, int right) const { if (left > right) { return 0; } return prefixSum(right) - prefixSum(left - 1); } }; struct RightComparator { bool operator()(const Query& a, const Query& b) const { if (a.right != b.right) { return a.right < b.right; } return a.left < b.left; } }; static u64 splitmix64(u64 x) { x += 0x9e3779b97f4a7c15ULL; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL; return x ^ (x >> 31); } // 同じ括弧列に属するクエリを、右端を一方向に動かして処理する。 static void solveGroup( const string& s, vector& queries, vector& answers ) { if (queries.empty()) { return; } const int n = static_cast(s.size()); // 文字列全体を左から貪欲に対応付ける。 // matchedPairs は閉じ括弧位置の昇順になる。 vector openStack; vector> matchedPairs; openStack.reserve(n); matchedPairs.reserve(n / 2); for (int position = 1; position <= n; ++position) { if (s[position - 1] == '(') { openStack.push_back(position); } else if (!openStack.empty()) { int openPosition = openStack.back(); openStack.pop_back(); matchedPairs.push_back({openPosition, position}); } } // クエリの右端を昇順にする。 sort(queries.begin(), queries.end(), RightComparator{}); FenwickTree fenwick(n); int pairIndex = 0; for (const Query& query : queries) { // 閉じ括弧位置が query.right 以下のペアを追加する。 while ( pairIndex < static_cast(matchedPairs.size()) && matchedPairs[pairIndex].second <= query.right ) { fenwick.add(matchedPairs[pairIndex].first, 1); ++pairIndex; } // 左括弧位置も [query.left, query.right] にあるペアを数える。 int pairCount = fenwick.rangeSum(query.left, query.right); answers[query.id] = pairCount * 2; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, Q; cin >> N >> Q; string initialS; cin >> initialS; vector operations(Q); for (int i = 0; i < Q; ++i) { cin >> operations[i].type >> operations[i].x >> operations[i].y; } // 各位置に2個の64 bit値を割り当てる。 vector weight1(N); vector weight2(N); for (int i = 0; i < N; ++i) { weight1[i] = splitmix64( 0x123456789abcdef0ULL + static_cast(i) ); weight2[i] = splitmix64( 0xfedcba9876543210ULL + static_cast(i) ); } string currentS = initialS; StateKey currentHash{0, 0}; // ')' である位置の値を XOR したものを状態ハッシュとする。 for (int i = 0; i < N; ++i) { if (currentS[i] == ')') { currentHash.h1 ^= weight1[i]; currentHash.h2 ^= weight2[i]; } } unordered_map groupId; groupId.reserve(static_cast(Q) * 2 + 1); groupId.max_load_factor(0.7f); vector groups; groups.reserve(Q); int answerCount = 0; // 1回目の走査:同じ括弧列に対するクエリ2を同じグループへ集める。 for (int time = 0; time < Q; ++time) { const Operation& operation = operations[time]; if (operation.type == 1) { int position = operation.x - 1; char nextCharacter = (operation.y == 1 ? '(' : ')'); if (currentS[position] != nextCharacter) { currentS[position] = nextCharacter; currentHash.h1 ^= weight1[position]; currentHash.h2 ^= weight2[position]; } } else { auto it = groupId.find(currentHash); int id; if (it == groupId.end()) { id = static_cast(groups.size()); groupId.emplace(currentHash, id); groups.push_back(Group{}); groups.back().representativeTime = time; } else { id = it->second; } groups[id].queries.push_back({ operation.x, operation.y, answerCount }); ++answerCount; } } // 各グループを、その状態が最初にクエリ2に登場した時刻で処理する。 vector> groupsAtTime(Q); for (int id = 0; id < static_cast(groups.size()); ++id) { groupsAtTime[groups[id].representativeTime].push_back(id); } vector answers(answerCount); currentS = initialS; // 2回目の走査:代表時刻の文字列を使って各グループを1回だけ解く。 for (int time = 0; time < Q; ++time) { const Operation& operation = operations[time]; if (operation.type == 1) { currentS[operation.x - 1] = (operation.y == 1 ? '(' : ')'); } else { for (int id : groupsAtTime[time]) { solveGroup(currentS, groups[id].queries, answers); } } } for (int answer : answers) { cout << answer << '\n'; } return 0; }