题目
题目大题就是给定一个括号序列,其中仅包含()*
,任务就是替换其中的*
为()
或者空字符串,是的原字符串平衡且字典序最小。
一个不难想象到的是,最终的解法应该是在字符串的两侧不断替换括号,且两种括号的添加不能发生交叉。试想,如果替换出现了这种情况...)...(...
。那么直接去掉这两个将会是长度更小的答案,所以这种情况是不会出现的。
为了解决问题,不妨将需要解决的问题分成两个步骤
- 使括号数量平衡
- 使括号配对平衡
首先拿到序列先在一侧添加括号,使得左右括号一样多。达成这一点很简单,扫描一遍整个数组即可。
在扫描的过程中,还能完成另一项任务:判定是否有解!!准确些说,就是对于一个位置前面的)
多于(
和*
的总和,或者一个位置后面的(
多于)
和*
的总和。这个在一次正向遍历中就能解决。
判断完结果后,把缺少的括号补充在一侧即可
scanf("%s",str);
n = strlen(str);
cntL = cntR = 0;
L = R = 0;
for(int i = 0;i < n && R >= 0;i++){
if(str[i] == '('){
L++;
cntL++;
}else{
(--L) < 0 && (L = 0);
}
if(str[i] == ')'){
R--;
cntR++;
}else{
R++;
}
}
if(L > 0 || R < 0){
printf("No solution!\n");
continue;
}
for(int i = 0;i < n && cntR - cntL > 0;i++){
if(str[i] == '*'){
str[i] = '(';
cntL++;
}
}
for(int i = n - 1;i >= 0 && cntL - cntR > 0;i--){
if(str[i] == '*'){
str[i] = ')';
cntR++;
}
}
经过上面的操作,两种括号的数量平衡了,可以说明的是现在待处理的问题串变成了这个亚子:....).)).))...(((.((....
省略掉的是*
和已经配好对的括号。且这里不难看出,两种未配对的括号各处一边,数量相等。 且经过上面的筛选,解是一定存在的。
下面的任务就是找到这些括号的数量了,有两种思路,一种是二分试出来,另一种是贪心的思想遍历一下找出来。
二分
对填充一种括号的数量进行二分,下限是0,上限是剩余*
数量的一半(因为括号要成对添加)。
每次枚举出数量放进去试一试,不合法就是少了,合法就是正好或者多了。
二分代码:
L = 0;
R = (n - cntL - cntR) >> 1;
int M,cnt;
while(L < R){
M = (L + R) >> 1;
strcpy(temp,str);
for(int i = n - 1,suf = M;i >= 0,suf > 0;i--){
if(temp[i] == '*'){
temp[i] = ')';
suf--;
}
}
cnt = 0;
for(int i = 0,pre = M;i < n && cnt >= 0;i++){
if(temp[i] == '('){
cnt++;
}else if(temp[i] == ')'){
cnt--;
}else if(pre > 0){
temp[i] = '(';
cnt++;
pre--;
}
}
if(cnt == 0){
R = M;
}else{
L = M + 1;
}
}
最终L就是要填充的括号数量
完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
static const int N = 1e5 + 50;
char str[N];
char temp[N];
int n,T;
int cntL,cntR;
int L,R;
int main(){
for(cin >> T;T;T--){
scanf("%s",str);
n = strlen(str);
cntL = cntR = 0;
L = R = 0;
for(int i = 0;i < n && R >= 0;i++){
if(str[i] == '('){
L++;
cntL++;
}else{
(--L) < 0 && (L = 0);
}
if(str[i] == ')'){
R--;
cntR++;
}else{
R++;
}
}
if(L > 0 || R < 0){
printf("No solution!\n");
continue;
}
for(int i = 0;i < n && cntR - cntL > 0;i++){
if(str[i] == '*'){
str[i] = '(';
cntL++;
}
}
for(int i = n - 1;i >= 0 && cntL - cntR > 0;i--){
if(str[i] == '*'){
str[i] = ')';
cntR++;
}
}
L = 0;
R = (n - cntL - cntR) >> 1;
int M,cnt;
while(L < R){
M = (L + R) >> 1;
strcpy(temp,str);
for(int i = n - 1,suf = M;i >= 0,suf > 0;i--){
if(temp[i] == '*'){
temp[i] = ')';
suf--;
}
}
cnt = 0;
for(int i = 0,pre = M;i < n && cnt >= 0;i++){
if(temp[i] == '('){
cnt++;
}else if(temp[i] == ')'){
cnt--;
}else if(pre > 0){
temp[i] = '(';
cnt++;
pre--;
}
}
if(cnt == 0){
R = M;
}else{
L = M + 1;
}
}
cnt = 0;
for(int i = 0;i < n && cnt < L;i++){
if(str[i] == '*'){
cnt++;
str[i] = '(';
}
}
cnt = 0;
for(int i = n - 1;i >= 0 && cnt < L;i--){
if(str[i] == '*'){
cnt++;
str[i] = ')';
}
}
for(int i = 0;i < n;i++){
if(str[i] != '*'){
printf("%c",str[i]);
}
}
printf("\n");
}
}
复杂度O(NlogN)
贪心
不同于二分,贪心就直接上去遍历整个序列。正向遍历,找出多出来的右括号的数量即可。
当右括号数量较多且碰到一个左括号的时候就将这些多出来的右括号计入答案随后重新统计。这样就能找出全部带匹配的右括号的数量,同时也是带匹配的左括号的数量。达成和二分一样的效果。
贪心部分:
cnt = 0;
L = 0;R = 0;
for(int i = 0;i < n;i++){
if(str[i] == '('){
if(cnt < 0){
L += -cnt;
R += -cnt;
cnt = 0;
}
cnt++;
}else if(str[i] == ')'){
cnt--;
}
}
最后L和R就是最终的答案,二者是一样的。
完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
static const int N = 1e5 + 50;
char str[N];
char temp[N];
int n,T;
int cntL,cntR;
int L,R;
int cnt;
int main(){
for(cin >> T;T;T--){
scanf("%s",str);
n = strlen(str);
cntL = cntR = 0;
L = R = 0;
for(int i = 0;i < n && R >= 0;i++){
if(str[i] == '('){
L++;
cntL++;
}else{
(--L) < 0 && (L = 0);
}
if(str[i] == ')'){
R--;
cntR++;
}else{
R++;
}
}
if(L > 0 || R < 0){
printf("No solution!\n");
continue;
}
for(int i = 0;i < n && cntR - cntL > 0;i++){
if(str[i] == '*'){
str[i] = '(';
cntL++;
}
}
for(int i = n - 1;i >= 0 && cntL - cntR > 0;i--){
if(str[i] == '*'){
str[i] = ')';
cntR++;
}
}
cnt = 0;
L = 0;R = 0;
for(int i = 0;i < n;i++){
if(str[i] == '('){
if(cnt < 0){
L += -cnt;
R += -cnt;
cnt = 0;
}
cnt++;
}else if(str[i] == ')'){
cnt--;
}
}
for(int i = 0;i < n && L;i++){
if(str[i] == '*'){
str[i] = '(';
L--;
}
}
for(int i = n - 1;i >= 0 && R;i--){
if(str[i] == '*'){
str[i] = ')';
R--;
}
}
for(int i = 0;i < n;i++){
if(str[i] != '*'){
printf("%c",str[i]);
}
}
printf("\n");
}
}