E. Rearrange Brackets
time limit per test:2 seconds
memory limit per test:256 megabytes
input:standard input
output:standard output
A regular bracket sequence is a bracket sequence that can be transformed into a correct arithmetic expression by inserting characters "1" and "+" between the original characters of the sequence. For example:
- bracket sequences "()()" and "(())" are regular (the resulting expressions are: "(1)+(1)" and "((1+1)+1)");
- bracket sequences ")(", "(" and ")" are not.
You are given a regular bracket sequence. In one move, you can remove a pair of adjacent brackets such that the left one is an opening bracket and the right one is a closing bracket. Then concatenate the resulting parts without changing the order. The cost of this move is the number of brackets to the right of the right bracket of this pair.
The cost of the regular bracket sequence is the smallest total cost of the moves required to make the sequence empty.
Actually, you are not removing any brackets. Instead, you are given a regular bracket sequence and an integer k�. You can perform the following operation at most k� times:
- extract some bracket from the sequence and insert it back at any position (between any two brackets, at the start or at the end; possibly, at the same place it was before).
After all operations are performed, the bracket sequence has to be regular. What is the smallest possible cost of the resulting regular bracket sequence?
Input
The first line contains a single integer t� (1≤t≤1041≤�≤104) — the number of testcases.
The first line of each testcase contains a single integer k� (0≤k≤50≤�≤5) — the maximum number of operations you can perform.
The second line contains a non-empty regular bracket sequence, it consists only of characters '(' and ')'.
The total length of the regular bracket sequences over all testcases doesn't exceed 2⋅1052⋅105.
Output
For each testcase, print a single integer — the smallest possible cost of the regular bracket sequence after you perform at most k� operations on it.
Example
input
7
0
()
0
(())
1
(())
5
()
1
(()()(()))
2
((())()(()())((())))
3
((())()(()())((())))
output
0
1
0
0
1
4
2
AC1:(c++)
First, let's define the cost of an RBS a bit clearer. The absolute smallest cost of removing each pair of brackets is the number of bracket pairs it's inside of. That can actually be achieved — just remove the pairs right to left (according to the positions of the opening brackets in pairs). So you can instead say that the total cost is the sum of balance values after all closing brackets. Or before all opening brackets — these are actually the same.
From that, we can code a classic dp. Imagine we are not moving brackets, but instead doing that in two separate movements: put a bracket in some buffer and place it in the string. We'd love to use dp[pos][open][close][moves]
— the smallest answer if we processed pos
brackets, open
opening brackets are in the buffer, close
closing brackets in the buffer and moves
are performed. Sadly, that doesn't really allow moving brackets to the left, since you would have to first place the bracket, then put in it the buffer. Does that actually break anything? Apparently, no. You can make these buffer states from −k
to k
, and think of negative values as taking a loan. These states are enough to determine the current balance of the string. Thus, enough to both update the states and check if the string stops being an RBS after placing a closing bracket.
Overall complexity: O(nk3)
.
We can do it faster, but our proof isn't that convincing.
Start by showing that there exists an optimal answer such that each move leaves the sequence an RBS. Consider a sequence of moves that ends up being an RBS. First, you can basically rearrange the moves (maybe adjusting the exact positions is required). Second, there exists a move that, performed first, leaves an RBS. Make it and propagate the proof. You can show that such a move exists by studying some cases.
Then I found it more intuitive to switch to another representation — you can look at the forest induced by the bracket sequence. The roots of the trees in it are the topmost opening and closing brackets of the RBS. Their children are the inner topmost brackets for each of them, and so on. With that representation, the answer is actually the sum of depths of all vertices.
Now for the moves. Let's move an opening bracket to the right. We won't move it after its corresponding closing bracket to not break an RBS. How will it change the tree? It will turn some children of the corresponding vertex into the children of its parent. Thus, it will decrease their depths by one, and the depths of their descendants as well. How about to the left? That will turn some children of its parent into its own children, increasing their depths (and the depths of their descendants) by one. Similar analysis can be performed for the closing brackets.
The claim is that, in the optimal answer, you should only move opening brackets and only to the right. Then they decrease the answer independently of each other. It's pretty clear that the best position to move each bracket to is as much to the right as possible — place it next to its respective closing bracket. That will decrease the answer by the size of the subtree (excluding the vertex itself).
Finally, we want to choose k
vertices that have the largest sum of their subtrees. That can be just done greedily — pick k
largest ones.
You don't have to build the tree explicitly for that — the size of the subtree is half of the number of brackets between an opening bracket and a corresponding closing one. So, everything can be processed with a stack.
Overall complexity: O(n)
or O(nlogn)
.
#include <bits/stdc++.h>
using namespace std;
#define forn(i, n) for(int i = 0; i < int(n); i++)
const long long INF64 = 1e18;
long long dp[2][11][11][6];
int main(){
int t;
cin >> t;
while (t--){
int k;
cin >> k;
string s;
cin >> s;
int n = s.size();
forn(balo, 2 * k + 1) forn(balc, 2 * k + 1) forn(mv, k + 1) dp[0][balo][balc][mv] = INF64;
dp[0][k][k][0] = 0;
int act = 0;
forn(ii, n){
int i = ii & 1;
int ni = i ^ 1;
forn(balo, 2 * k + 1) forn(balc, 2 * k + 1) forn(mv, k + 1)
dp[ni][balo][balc][mv] = INF64;
forn(mv, k + 1) forn(balo, 2 * k + 1) forn(balc, 2 * k + 1) if (dp[i][balo][balc][mv] != INF64){
int bal = act - balo + balc;
if (balo >= 0 && mv < k)
dp[i][balo - 1][balc][mv + 1] = min(dp[i][balo - 1][balc][mv + 1], dp[i][balo][balc][mv] + bal);
if (balc >= 0 && mv < k && bal > 0)
dp[i][balo][balc - 1][mv + 1] = min(dp[i][balo][balc - 1][mv + 1], dp[i][balo][balc][mv]);
if (s[ii] == '('){
dp[ni][balo][balc][mv] = min(dp[ni][balo][balc][mv], dp[i][balo][balc][mv] + bal);
if (balo + 1 <= 2 * k)
dp[ni][balo + 1][balc][mv] = min(dp[ni][balo + 1][balc][mv], dp[i][balo][balc][mv]);
}
else{
if (bal > 0)
dp[ni][balo][balc][mv] = min(dp[ni][balo][balc][mv], dp[i][balo][balc][mv]);
if (balc + 1 <= 2 * k)
dp[ni][balo][balc + 1][mv] = min(dp[ni][balo][balc + 1][mv], dp[i][balo][balc][mv]);
}
}
act += (s[ii] == '(' ? 1 : -1);
}
printf("%lld\n", *min_element(dp[n & 1][k][k], dp[n & 1][k][k] + k + 1));
}
}
AC2:(c++)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define deb(x) cerr<<"Line: "<<__LINE__<<", val= "<<x<<"; \n"
#define pii pair<ll,ll>
#define mp make_pair
#define fi first
#define se second
const ll N=2e5+10;
ll T,n,k,L[N],dep[N];
char c[N];
void work(){
scanf("%lld",&k);
scanf("%s",c+1);
n=strlen(c+1);
ll de=0,ans=0;
stack<ll> s;
vector<pii> v;
for(ll i=1;i<=n;i++){
if(c[i]=='('){
de++;
s.push(i);
}
else{
de--;
dep[i]=de;
ans+=de;
L[i]=s.top();
s.pop();
v.push_back(mp(-i+L[i]-1,i));
}
}
sort(v.begin(),v.end());
for(int i=0;i<min(ll(v.size()),k);i++){
ans-=-v[i].fi/2-1;
}
printf("%lld\n",ans);
}
signed main(){
scanf("%lld",&T);
while(T--){
work();
}
return 0;
}