[HDU2020]第三场 1009 Parentheses Matching (二分/贪心)

Parentheses Matching

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6799
Problem Description
Given a string P consisting of only parentheses and asterisk characters (i.e. “(”, “)” and “*”), you are asked to replace all the asterisk characters in order to get a balanced parenthesis string with the shortest possible length, where you can replace each “*” by one “(”, or one “)”, or an empty string “”.

A parenthesis string S is a string consisting of only parentheses (i.e. “(” and “)”), and is considered balanced if and only if:

● S is an empty string, or
● there exist two balanced parenthesis strings A and B such that S=AB, or
● there exists a balanced parenthesis string C such that S=(C).

For instance, “”, “()”, “(())”, “()()”, “()(())” are balanced parenthesis strings.

Due to some notorious technical inability, if there are several solutions with the shortest possible length, then you have to report the smallest possible one in lexicographical order.

For every two different strings A and B of the same length n, we say A is smaller than B in lexicographical order if and only if there exists some integer k such that:

● 1≤k≤n, and
● the first (k−1) characters of A and that of B are exactly the same, and
● the k-th character of A is smaller than that of B.

For instance, “()(())” is smaller than “()()()”, and in this case, k=4.

Input
There are several test cases.

The first line contains an integer T (1≤T≤ 1 0 5 10^5 105), denoting the number of test cases. Then follow all the test cases.

For each test case, the only line contains a string of length n (1≤n≤ 1 0 5 10^5 105), denoting the string P that consists of only parentheses and asterisk characters.

It is guaranteed that the sum of n in all test cases is no larger than 5× 1 0 6 10^6 106.

Output
For each test case, output in one line “No solution!” (without quotes) if no solution exists, or otherwise the smallest possible solution in lexicographical order. Note that the output characters are case-sensitive.

Sample Input

5
*))*)
*(*)*
*)*(*
******
((***)()((**

Sample Output

No solution!
()
()()

(())()(())

题面

在这里插入图片描述
在这里插入图片描述

思路

在这里插入图片描述
题意;
给定一个括号序列,其中仅包含 ( ( (   ) ) )  和   ∗ * ,需要替换其中的 ∗ * ( ( ( 或者  ) ) )或者空字符串,使得原字符串平衡且字典序最小。

题解看的有点迷惑,这里参考一下博主wayne_lee_lwc写的https://blog.csdn.net/wayne_lee_lwc/article/details/107659395
大概明白一点,这里再解释下:
在这里插入图片描述
这里交叉的意思指,我们从后往前替换成右括号,从前往后替换为左括号,那么 替换为右括号的位置 不可能在 替换为左括号的位置 的左边。

所以解决问题就是两步解决:
(1)使括号数量平衡 =>(2)使括号配对平衡
在这里插入图片描述

首先完成第一个任务,并且在此过程中可以判断是否有解。就是保证对于任意一个位置,不存在 一个位置前面的 ) ) ) 多于 ( ( ( ∗ * 的总和,或者一个位置后面的 ( ( ( 多于 ) ) ) ∗ * 的总和。

代码如下:
可以参考下写的注解

scanf("%s",str);
n = strlen(str);
cntL = cntR = 0;  //统计字符串中左括号和右括号的个数
L = R = 0;  //L统计在当前位置的)和*的个数的(的个数
for(int i = 0;i < n && R >= 0;i++){  //限定了R>=0
	if(str[i] == '('){
		L++;
		cntL++;
	}else{  //如果是)或者*
		(--L) < 0 && (L = 0);  //将L-1,如果L小于0就置为0,这个位置前面的都满足)的个数+*的个数 - (的个数 >= 0,此时可能不满足R >= 0而无解,但是在这个位置对于 ) 和 * 的总和大于 ( 的个数这个要求是满足的,重新置为0,判断这个位置后面的情况。
	}
	
	if(str[i] == ')'){
		R--;
		cntR++;
	}else{  //如果是(或者*
		R++;
	}
}

//L > 0 表示遍历完数组后,说明存在某个位置,这个位置后面 ( 的个数多余 )和 * 的总和,故无解。
//R < 0会跳出循环,R < 0表示在当前位置前面的 ) 多于 ( 和 * 的总和,故无解
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,R 和上面不一样,变成了上下限

L = 0;//下限
R = (n - cntL - cntR) >> 1; //上限
int M,cnt;
while(L < R){
	M = (L + R) >> 1;  //中间值
	strcpy(temp,str);
	//suf即所中间值,所试的填充的右括号数量
	//先从后往前填充完右括号
	for(int i = n - 1,suf = M;i >= 0,suf > 0;i--){
		if(temp[i] == '*'){
			temp[i] = ')';
			suf--;
		}
	}
	//接下来从左往右填充左括号并且验证是否合法,pre填充的左括号数量
	cnt = 0;  //cnt来判断是否合法,如果cnt==0就是合法的
	for(int i = 0,pre = M;i < n && cnt >= 0;i++){  //限制cnt >= 0,如果小于0了,说明填充的数量少了
		if(temp[i] == '('){
			cnt++;
		}else if(temp[i] == ')'){
			cnt--;
		}else if(pre > 0){  //最左边的*填充为(
			temp[i] = '(';
			cnt++;
			pre--;
		}
	}
	
	if(cnt == 0){  //合法,将上限换为现在的值
		R = M;
	}else{  //不合法说明少了,将下限换为M+1
		L = M + 1;
	}
}

最终L=R跳出循环,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");
	}
}

贪心

在这里插入图片描述

意思指从左往右遍历,碰到)(多的情况就是需要在这段里面填充左括号了,填充的数量就是多的个数,所以将这一段多的统计如需要填充的括号数量里,然后再重新开始判断后面还有没有)(多的情况,是否还需要填充括号。

代码如下:
cnt就是当前位置之前)(两者数量之差

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");
	}
}

如果用贪心的思想的话,直接用栈和队列也行。
显然如果*可以为空是最好的,不能的话就得变为括号,左括号越左越好,右括号就从最右替换是符合规则的。
从左到右,用栈维护左括号的位置,另一个双向队列(或者用数组)维护还没使用的星号的位置。
当遇到左括号就将其位置(下标)压入栈,遇到星号就将其位置(下标)压入队列(或数组)。
如果遇到右括号:
I f If If   ( 前 边 有 左 括 号 可 以 抵 消 ) (前边有左括号可以抵消) ()
这 里 不 用 管 左 括 号 的 位 置 是 最 左 还 是 离 这 个 右 括 号 最 近 , 直 接 抵 消 就 可 以 了 , 所 以 直 接 用 栈 来 就 可 以 , 直 接 出 栈 , 匹 配 一 个 左 括 号 。 不 能 就 看 看 能 不 能 和 最 小 的 星 位 置 结 合 , 如 果 再 不 行 就 无 解 。 这里不用管左括号的位置是最左还是离这个右括号最近,直接抵消就可以了,所以直接用栈来就可以,直接出栈,匹配一个左括号。不能就看看能不能和最小的星位置结合,如果再不行就无解。
E l s e   / / 前 边 没 有 未 匹 配 的 左 括 号 可 以 抵 消 了          i f Else\ //前边没有未匹配的左括号可以抵消了 \\ \ \ \ \ \ \ \ \ if Else //        if   ( 前 面 还 有 没 用 的 ∗ 可 以 用 来 替 换 为      ′   (     ′ ) (前面还有没用的*可以用来替换为 \ \ \ ' \ ( \ \ ') (    (  )
             那 么 就 把 最 左 边 的 ∗ 替 换 为 左 括 号 跟 他 匹 配 \ \ \ \ \ \ \ \ \ \ \ \ 那么就把最左边的*替换为左括号跟他匹配             
         e l s e   / / 又 没 得 ∗ 可 以 用              那 么 就 无 解 \ \ \ \ \ \ \ \ else \ //又没得*可以用 \\ \ \ \ \ \ \ \ \ \ \ \ \ 那么就无解         else //            

这样子结束了之后,右括号就都有匹配的左括号,这时左括号可能还有多的。
结束之后如果栈里面还有左括号,那就用最右边位置的*替换为)去和它结合。直到(没了或者*没了。
如果左括号没了说明有解,如果*用完了,左括号还没匹配完就说明无解了。

代码如下:

#include <stack>
#include <cstring>
#include <iostream>
#include <deque>

using namespace std;

const int maxn = 2e5 + 7;
char s[maxn];
stack<int>stk;
deque<int>q;

int main() {
    int T; 
    scanf("%d", &T);
    while (T--) {
        while (!stk.empty()) { //清空栈和队列
            stk.pop();
        }
        while (!q.empty()) {
            q.pop_back();
        }
        scanf("%s", s + 1);
        int n = strlen(s + 1);
        int ok = 1;
        for (int i = 1; i <= n; i++) {  //遍历字符串
            if (s[i] == '*') {  //如果是'*',把位置压入队列
                q.push_back(i);
            }
            if (s[i] == ')') {   //如果是')',找是否匹配,不匹配能否调整
                if (!stk.empty()) { //如果有左括号就出栈
                    stk.pop();
                }
                else if (!q.empty()) {  //如果没有左括号但是有'*'
                    s[q.front()] = '(';  //把最左边'*'改为'('
                    q.pop_front();     //出列
                }
                else {  //无法匹配'(',无解
                    ok = 0;
                    break;
                }
            }
            if (s[i] == '(') {   //如果是'(',吧位置压入栈中
                stk.push(i);
            }
        }
        //匹配完')'了,如果还有'('
        while (!stk.empty()) {
            if (!q.empty() && q.back() > stk.top()) {  //如果处在最右边的'*'的位置在'('的右边
                s[q.back()] = ')'; //变为')'
                stk.pop();//'('出栈
                q.pop_back();//出列
            }
            else {  //否则,连最右边都没有符合的就无解了
                ok = 0;
                break;
            }
        }

        if (!ok) {
            printf("No solution!\n");
        }
        else {
            for (int i = 1; i <= n; i++) {
                if (s[i] == '(' || s[i] == ')')
                    printf("%c", s[i]);
            }
            printf("\n");
        }
    }
}


如果直接用数组来做栈和队列
实现如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 50;

char s[maxn];
int star[maxn * 5], head, tail;
int stk[maxn], top;
int ans[maxn];  //-1表示左括号,1表示右括号

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%s", s);
        top = head = tail = 0;
        int n = strlen(s);
        for (int i = 0; i < n; i++) {
            ans[i] = 0;
        }
        int ok = 1;
        for (int i = 0; i < n; i++) {
            if (s[i] == '*') {
                star[tail++] = i;
            }
            else if (s[i] == '(') {
                stk[top++] = i;
            }
            else if (s[i] == ')') {
                if (top > 0) { //如果有左括号
                    top--;
                }
                else if (head < tail) {  //如果没有左括号但是有*
                    ans[star[head]] = -1;//标记为左括号
                    head++;
                }
                else {  //啥也没有
                    ok = 0;
                    break;
                }
            }
        }
        if (!ok) {
            printf("No solution!\n");
            continue;
        }
        //补完左括号配对右括号后还有左括号
        while (top > 0) {
            if (tail > head&& star[tail - 1] > stk[top - 1]) {
                top--;
                ans[star[tail - 1]] = 1;//标记为右括号
                tail--;
            }
            else {
                ok = 0;
                break;
            }
        }
        if (!ok) {
            printf("No solution!\n");
            continue;
        }
        else {
            for (int i = 0; i < n; i++) {
                if (s[i] != '*') {
                    printf("%c", s[i]);
                }
                else if (ans[i] == -1) {
                    printf("(");
                }
                else if (ans[i] == 1) {
                    printf(")");
                }
            }
            printf("\n");
        }

    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值