前言
好题。
思路分析
一个朴素的想法是,对于每种字符,我们决策它放的位置,做四路归并,这样复杂度为 \(O(n^4)\)。
但是这样显然没优化前途。考虑做一些观察。
-
o 存在与否并不重要:o 放在任何位置都是合法的,所以为了最小化代价,我们把 o 放在原来的位置即可;
-
() 的移动方案和 x 的移动方案是独立的:对于 x 来说,只要存在合法的 (),那么它一定可以放在 () 中间,而具体是哪个 ( 和哪个 ) 匹配并不重要。
有了这两个性质,我们就可以做 () 和 ox 的两路归并了,复杂度为 \(O(n^2)\)。
具体地,设 \(f_{i,j}\) 表示一路枚举到 \(i\),一路枚举到 \(j\) 的最小代价,从 \(f_{i,j-1}\) 和 \(f_{i-1,j}\) 转移即可。
代码实现
/*
没啥细节,不写了贺一个
*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN=8005;
char s[MAXN];
int n,m,sz,a[MAXN],b[MAXN],w[MAXN],dp[MAXN][MAXN],fa[MAXN][MAXN],fb[MAXN][MAXN];
inline void chkmin(int &x,const int &y) { x=x<y?x:y; }
signed main() {scanf("%s",s+1),sz=strlen(s+1);for(int i=1;i<=sz;++i) {if(s[i]=='('||s[i]==')') a[++n]=i;else b[++m]=i;}for(int i=1;i<=n;++i) {w[i]=w[i-1]+(s[a[i]]=='('?1:-1);if(w[i]<0) {for(int j=i+1;;++j) if(s[a[j]]=='(') {rotate(a+i,a+j,a+j+1); break;}w[i]+=2;}}memset(dp,0x3f,sizeof(dp));dp[0][0]=0;for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) dp[0][0]+=a[j]<a[i];for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {fa[i][j]=fa[i][j-1]+(a[i]<b[j]);fb[i][j]=fb[i-1][j]+(b[j]<a[i]);}for(int i=0;i<=n;++i) for(int j=0;j<=m;++j) {if(i<=n) chkmin(dp[i+1][j],dp[i][j]+fa[i+1][j]);if(j<=m&&(w[i]||s[b[j+1]]=='o')) chkmin(dp[i][j+1],dp[i][j]+fb[i][j+1]);}printf("%d\n",dp[n][m]);return 0;
}