题目来源:
HihoCoder 1300题目要求:
对于一个仅由“(”和“)”两种字符构成的字符串,如果它满足下面两个条件,就说它是“合法”的:
① 整个字符串中的“)”和“)”的数目是相同的。
② 在所有该字符串的前缀中,“(”的数目一定不小于“)”的数目。
现给出一个由“(”和“)”组成的字符串,求解其中有多少个子串是合法的。
解答:
本题采用动态规划的思路进行解答,记给出的字符串为s[i], (i = 1, 2, ... n)。定义状态如下:
dp[i]: 以s[i]为结尾的合法子串的数目。
计算dp[i]的方式如下:
(1) 首先,如果s[i]是左括号,即s[i] = '(',那么:
dp[i] = 0
也就是说,以左括号为结尾的子串都是不合法的。原因在于,假设有一子串:s[j], s[j+1], ..., s[i], 那么根据条件①,该子串中的左括号和右括号的数目一定是相同的,那么它的前缀:s[j], [sj+1], ..., s[i-1] 中的右括号数目一定比左括号的数目多1,这不满足条件②,因此它是不合法的。
(2)如果s[i]是右括号,即s[i] = ')',并且它对应的左括号为s[j], 那么:
dp[i] = dp[j - 1] + 1
原因在于,因为s[i]对应的左括号为s[j], 因此,整个字符串的结构一定是下面的样子:
s[1] s[1] .... s[j-1] ( s[j+1] ... s[i-1] ) s[i+1] ... s[n]
此时,子串:s[j], s[j+1] ... s[i-1], s[i] 一定是一个合法的子串,因为它如果不满足条件①或②,都无法使得s[i]对应的左括号是s[j],因此将s[j-1]结尾的合法子串和子串:s[j], s[j+1] ... s[i-1], s[i] 拼接起来,就是以s[i]为结尾的合法子串,另外,单独的子串:s[j], s[j+1] ... s[i-1], s[i] 也是一个以s[i]结尾的合法子串,因此:
dp[i] = dp[j-1] + 1;
(3) 如果某一个右括号s[i]没有匹配的左括号,那么,所有以它结尾的子串都是不合法的:
dp[i] = 0
(4) 为了保证迭代求解过程的顺利进行,算法开始时需要一个初始的值,这个值我们设为dp[0]表示空串情况下的合法子串数目,显然:
dp[0] = 0。
采用一个栈来计算每个右括号对应的左括号,每遇到一个左括号,就将其入栈,遇到右括号,那么当前的栈顶元素就是该右括号对应的左括号,将其出栈即可。
最后,将dp[0], dp[1] ... dp[n]的数值累加起来,就是题目需要的答案。
输入输出格式:
输入:输入数据仅一行,包含一个长度为n的括号字符串。
输出:输出一行,表示合法的括号子串的数目。
数据范围:
1 ≤ n ≤ 1,000,000
程序代码:
/****************************************************/
/* File : HihoCoder1300 */
/* Author : Zhang Yufei */
/* Date : 2016-05-18 */
/* Description : HihoCoder ACM program. (submit:g++)*/
/****************************************************/
#include<stdio.h>
#include<string.h>
/*
* The main program.
*/
int main(void) {
char n[1000001];
scanf("%s", n);
int length = strlen(n);
int stack[length];
int sum[length + 1];
int top = -1;
sum[0] = 0;
int count = 0;
for(int i = 0; i < length; i++) {
if(n[i] == '(') {
top++;
stack[top] = i;
sum[i + 1] = 0;
} else {
if(top == -1) {
sum[i + 1] = 0;
} else {
sum[i + 1] = sum[stack[top]] + 1;
top--;
}
}
count += sum[i + 1];
}
printf("%d\n", count);
return 0;
}