CodeForces 3D Least Cost Bracket Sequence 题解
这道题(不看范围)一看就可以dp。
\(dp[i][j]\)表示考虑到第i位,左括号的数量减右括号的数量 的值为j。
可以推出:\(dp[i][j]=max(dp[i-1][j-1]+a[i],dp[i-1][j+1]+b[i])\)。
观察发现,有些dp值是没有用的,不需要转移,可以用一个vector存下有用的dp值。
#include<bits/stdc++.h>
using namespace std;
typedef long long llint;
typedef pair< int, int > par;
const int MaxN = 50005;
const int INF = 1000000000;
const llint inf = 1LL<<60;
char s[ MaxN ];
llint dp[2][ MaxN ];
char f[2][ MaxN ];
int l[2][ MaxN ], c[2];
int a[ MaxN ], b[ MaxN ];
int fw[ MaxN ];
vector< par > v;
int main( void ) {
scanf( "%s", s );
int n = strlen( s );
for( int i = 0; i < n; ++i ) {
if( s[i] == '(' ) b[i] = INF;
if( s[i] == ')' ) a[i] = INF;
if( s[i] == '?' ) scanf( "%d %d", a+i, b+i );
}
for( int i = 0; i <= n; ++i )
dp[0][i] = dp[1][i] = inf;
v.reserve( 100*n );
dp[0][0] = 0;
l[0][ c[0]++ ] = 0;
int now = 1, next = 0;
for( int i = 0; i <= n; ++i ) {
now ^= 1, next ^= 1;
int tmp = v.size();
fw[i] = tmp;
c[next] = 0;
llint d;
for( int w = 0; w < c[now]; ++w ) {
int j = l[now][w];
if( f[now][j] ) v.push_back( par( i, j ) );
d = dp[now][j];
if( j > 0 && d+b[i] < dp[next][j-1] ) {
if( dp[next][j-1] >= inf ) l[next][ c[next]++ ] = j-1;
dp[next][j-1] = d+b[i], f[next][j-1] = 0;
}
if( j+1 <= ( n-i-1 ) && d+a[i] < dp[next][j+1] ) {
if( dp[next][j+1] >= inf ) l[next][ c[next]++ ] = j+1;
dp[next][j+1] = d+a[i], f[next][j+1] = 1;
}
if( i != n ) dp[now][j] = inf;
}
if( v.size() > tmp ) sort( v.begin() + tmp, v.end() );
}
fw[n+1] = v.size();
if( dp[now][0] >= inf ) { puts( "-1" ); return 0; }
int x = n, y = 0;
printf( "%lld\n", dp[now][0] );
for( int i = n; i > 0; --i ) {
int lo = fw[i], hi = fw[i+1]-1;
while( lo < hi ) {
int mid = ( lo+hi )/2;
if( v[mid].second > y ) hi = mid-1; else
lo = mid;
}
int w = 0;
if( lo <= hi && v[lo].second == y ) w = 1;
if( w == 1 ) s[i-1] = '(', y--; else
s[i-1] = ')', y++;
}
puts( s );
return 0;
}
记忆化搜索也可以。
#include<bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<(n);++i)
#define FORE(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();++i)
typedef long long ll;
const int N=51000;
const ll inf = ll(1000000000)*10000000;
int n,A[N],B[N],zz;
char b[N],z[N];
map<unsigned int,ll> mapka;
ll rek(int i, int otw) {
unsigned int key = (unsigned int)i*N+(otw+2);
if (mapka.find(key) != mapka.end()) return mapka[key];
if (otw<0) return inf;
if (otw>n-i) return inf;
if (i==n) return otw==0 ? 0 : inf;
if (b[i]==')') {
return mapka[key] = rek(i+1,otw-1);
}
else if (b[i]=='(') {
return mapka[key] = rek(i+1,otw+1);
}
else {
return mapka[key] = min( rek(i+1,otw-1) + B[i], rek(i+1,otw+1) + A[i] );
}
}
void pr(int i, int otw) {
if (i==n) return;
if (b[i]==')') z[zz++]=')', pr(i+1,otw-1);
else if (b[i]=='(') z[zz++]='(', pr(i+1,otw+1);
else if (rek(i,otw) == rek(i+1,otw-1) + B[i])
z[zz++]=')', pr(i+1,otw-1);
else z[zz++]='(', pr(i+1,otw+1);
}
int main() {
gets(b); n=strlen(b);
REP(i,n) if (b[i]=='?') scanf("%d%d",&A[i],&B[i]);
ll ans=rek(0,0);
if (ans>=inf) puts("-1");
else {
printf("%lld\n",ans);
pr(0,0);
puts(z);
}
}
再看看数据范围,\(n \leq 5 \times 10 ^ 4\) , \(O(n^2)\) 实在是危险,不过我们还有优化呢,不怕!
事实上,这两个程序都是过不了的,虽然还有小的优化余地,但是加上,在\(CF\)上用处不大。
考虑正解。有各种贪心。
首先比较简单的两种,用优先队列的。
第一种,我们把左括号、右括号都看成问号,原来的左括号变成左括号的代价是\(0\),变成右括号的代价为\(inf\),原来的右括号同理。可以知道,这样它们会强制变回原来的类型(否则一定无解)。计算每个位置上两个代价的差 ,\(dif_i=a_i-b_i\)。
这样以后,把所有都强制变成右括号。从左往右扫,在一定的时间,把前面的一个右括号变成左括号。这里,选\(dif_i\)最小的那个,这样才能让代价最低。
什么时候这样做呢?实在不能不改时,我们再改(明显,改得越迟,最小的\(dif_i\)也越小)。所以,在前面序列长度是奇数时,就改(如果长度为偶数改,最后一个可能变成右括号,再晚的话,更不行)。用优先队列维护所有可行括号的\(dif\)值。
(注意,代码中实现有些不同,比如0-indexed,没有真的写出\(dif\),和优先队列那里取了相反数)
#include<bits/stdc++.h>
using namespace std;
typedef unsigned uint;
typedef long long Int;
typedef vector<int> vint;
typedef pair<int,int> pint;
#define mp make_pair
template<class T> void pv(T a, T b) { for (T i = a; i != b; ++i) cout << *i << " "; cout << endl; }
int in_c() { int c; for (; (c = getchar()) <= ' '; ) { if (!~c) throw ~0; } return c; }
int in() {
int x = 0, c;
for (; (uint)((c = getchar()) - '0') >= 10; ) { if (c == '-') return -in(); if (!~c) throw ~0; }
do { x = (x << 3) + (x << 1) + (c - '0'); } while ((uint)((c = getchar()) - '0') < 10);
return x;
}
const Int INF = 1001001001001LL;
int N;
char S[50010];
int main() {
int i;
Int a, b;
Int ans = 0;
priority_queue< pair<Int,int> > q;
scanf("%s", S);
N = strlen(S);
for (i = 0; i < N; ++i) {
switch (S[i]) {
case '(': {
a = 0;
b = INF;
} break; case ')': {
a = INF;
b = 0;
} break; default: {
S[i] = ')';
a = in();
b = in();
}
}
ans += b;
q.push(mp(b - a, i));
if (~i & 1) {
ans -= q.top().first;
S[q.top().second] = '(';
q.pop();
}
}
if (ans >= INF) {
puts("-1");
} else {
printf("%lld\n", ans);
puts(S);
}
return 0;
}
第二种,不对原来的左右括号特殊处理,用括号问题的常规操作,存下
当前前缀左括号数量减右括号数量的值$ now \(。把所有"?"当作右括号,放入一个优先队列(set),当某次\)now < 0$时,再把前面一个右括号修改。其他基本相同。
代码用了set,所以正着写。当然还用优先队列也可以。
#include<bits/stdc++.h>
std::set<std::pair<int,int> > S;
char s[55555];
int a[55555];
int b[55555];
int main()
{
long long res=0;
int i,n,now;
scanf("%s",s);
n=strlen(s);
for(i=0;i<n;i++)if(s[i]=='?')scanf("%d%d",&a[i],&b[i]);
now=0;
for(i=0;i<n;i++)
{
if(s[i]=='(')now++;
else if(s[i]==')')now--;
else
{
s[i]=')';
res+=b[i];
now--;
S.insert(std::make_pair(a[i]-b[i],i));
}
if(now<0)
{
if(S.empty())
{
puts("-1");
return 0;
}
now+=2;
res+=S.begin()->first;
s[S.begin()->second]='(';
S.erase(S.begin());
}
}
if(now!=0)
{
puts("-1");
return 0;
}
printf("%lld\n%s",res,s);
}
还有比较奇葩的做法。
其一。不妨把所有问号,全改成代价较小的那种,然后再慢慢调整。
维护两个set \(up\) 和 \(down\) 。\(cur\)还是维护左右括号差值。
每遇到一个问号,更改它的类型后,按照新的类别插入set,'('插入\(down\),')'插入\(up\),同时设一个参数\(dif = |a_i-b_i|\),作为pair第一个元素,下标作为第二个(对于\(pair\),set先按照第一个元素排,再按照第二个元素排)。\(up\)直接用来改,\(down\) 留到后面用。
也是在\(cur<0\)时,从 \(up\) 里拿出 \(dif\) 最小的(\(up\) 里全是右括号),更改那个位置。还要把 \(down\) 清空,因为这里前缀和已经小了,以后再把前面的左改成右,一定又不合法了。
终于到了用 \(down\) 的时候了。也是每次取 \(dif\) 最小的,但改之后,必须要检查不考虑左右括号总数时,序列是否合法。这个直接拿线段树维护,这里合法的条件是,所有位置的最小值不小于\(0\)。重复执行,直到左右括号总数相等。
运行过程中,发现 \(up\) 或 \(down\) 不够用,直接\(-1\)就好了。
#include<bits/stdc++.h>
#define mp make_pair
#define sz(v)((int)((v).size()))
#define all(v) v.begin(),v.end()
#define pb push_back
using namespace std;
typedef pair<int, int> ii;
typedef long long int64;
typedef vector<int> vi;
template<class T> T abs(T x) {return x > 0 ? x : (-x);}
template<class T> T sqr(T x) {return x * x;}
int a[100000][2];
void save()
{
printf("-1\n");
exit(0);
}
const int n = 65536;
int tree[57000 * 4 + 10];
int delta[57000 * 4 + 10];
void Set(int pos, int newval, int i = 1, int l = 0, int r = n - 1)
{
if (l == r)
{
tree[i] = newval;
return;
}
int m = (l + r) / 2;
if (pos <= m)
Set(pos, newval, i * 2, l, m);
else
Set(pos, newval, i * 2 + 1, m + 1, r);
tree[i] = min(tree[2 * i], tree[2 * i + 1]);
}
void multiinc(int l0, int r0, int val, int i = 1, int l = 0, int r = n - 1)
{
if (l >= l0 && r <= r0)
{
delta[i] += val;
return;
}
if (min(r, r0) < max(l, l0)) return;
int m = (l + r) / 2;
multiinc(l0, r0, val, 2 * i, l, m);
multiinc(l0, r0, val, 2 * i + 1, m + 1, r);
tree[i] = min(tree[2 * i] + delta[2 * i], tree[2 * i + 1] + delta[2 * i + 1]);
}
int get(int l0, int r0, int i = 1, int l = 0, int r = n - 1)
{
if (min(r, r0) < max(l, l0))
return 100000000;
if (l >= l0 && r <= r0)
return tree[i] + delta[i];
int m = (l + r) / 2;
return delta[i] + min(get(l0, r0, 2 * i, l, m), get(l0, r0, 2 * i + 1, m + 1, r));
}
int main()
{
memset(tree, 0, sizeof(tree));
string s;
cin >> s;
for (int i = 0; i < sz(s); i++)
{
if (s[i] != '?') continue;
scanf("%d%d", &a[i][0], &a[i][1]);
}
set<ii> up, down;
int cur = 0;
int64 cost = 0;
for (int i = 0; i < sz(s); i++)
{
if (s[i] == '?')
{
if (a[i][0] < a[i][1])
{
s[i] = '(';
cost += a[i][0];
down.insert(ii(a[i][1] - a[i][0], i));
cur++;
}
else
{
s[i] = ')';
cost += a[i][1];
up.insert(ii(a[i][0] - a[i][1], i));
cur--;
}
}
else
{
cur += s[i] == '(' ? 1 : -1;
}
if (cur < 0)
{
if (up.empty()) save();
set<ii>::iterator it = up.begin();
cost += it->first;
s[it->second] = '(';
cur += 2;
up.erase(it);
down.clear();
}
}
int ss = 0;
for (int i = 0; i < sz(s); i++)
{
ss += s[i] == '(' ? 1 : -1;
Set(i + 1, ss);
}
while (cur > 0)
{
if (down.empty()) save();
set<ii>::iterator it = down.begin();
int delt = it->first;
int ind = it->second;
down.erase(it);
multiinc(ind + 1, sz(s), -2);
int opt = get(1, sz(s));
if (opt < 0)
{
multiinc(ind + 1, sz(s), 2);
continue;
}
cur -= 2;
s[ind] = ')';
cost += delt;
}
cout << cost << "\n" << s << "\n";
return 0;
}
其二。先想办法把无解情况排除。由左右括号的数量和问号的数量,可以算出有多少问号要改成左或右括号,分别记为\(A\)和\(B\)。
最可能让序列合法的改法是,前\(A\)个放左括号,后\(B\)个放右括号。如果这都不行,肯定无解。
然后就以这个改好的序列为基础,找最优解。类似前面,对于所有问号,设\(dif=a_i-b_i\)(没有绝对值),按照这个(从小到大)把它们的id排序。
排完序,排在前面的\(b_i\)肯定比\(a_i\)大的较多,我们想把它们改成左括号。
神奇的来了。现在设一个指针 \(lst\) ,表示最后一个由问号转过来,且没有被当前过程确认过的左括号的下标,维护先不谈。
如果新序列中该位置是'(',先保留。否则,我们尽量尝试把它改成左括号,同时把 \(lst\) 位置上的改成右括号。这样改肯定是希望最大的了,如果更靠前,变化量只会更大。用前缀和维护之。
于是,会发现 \(lst\) 是单调不升的,用类似two-pointers的写法就行。原因是,\(lst\)右边全是右括号或者已经固定的左括号,新的操作并不能改变右边的局面(右边出现更改,顶多是把右括号变成固定的左括号,\(lst\)一定在当前操作位置的左边)。
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define pb push_back
#define rep(i,n) for(int i = 0; i < n; i++)
#define re return
#define fi first
#define se second
#define sz(x) ((int) (x).size())
#define all(x) (x).begin(), (x).end()
#define sqr(x) ((x) * (x))
#define y0 y3487465
#define y1 y8687969
typedef vector<int> vi;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<ii> vii;
typedef vector<string> vs;
typedef vector<vi> vvi;
template<class T> T abs(T x) {
re x > 0 ? x : -x;
}
int n;
int m, last;
char w[100000], ww[100000];
int a[100000], b[100000], dd[100000], prev[100000];
vector<pair<int, int> > v;
int check (int x) {
if (last == -1) return 0;
for (int i = x; i >= last; i--)
if (dd[i] < 2)
return 0;
for (int i = last; i <= x; i++) dd[i] -= 2;
ww[last] = ')';
ww[x + 1] = '(';
last--;
while (last >= 0 && (w[last] != '?' || ww[last] == ')')) last--;
return 1;
}
int go (int a, int b) {
int d = 0;
for (int i = 0; i < n; i++) {
char c = w[i];
if (c == '?')
if (a > 0) { last = i; c = '('; a--; } else { c = ')'; b--; }
d += int (c == '(') - int (c == ')');
if (d < 0) return 1;
ww[i] = c;
dd[i] = d;
}
if (d != 0) return 1;
return 0;
}
int main() {
gets (w);
n = strlen (w);
int d = 0, m = 0;
for (int i = 0; i < n; i++)
if (w[i] == '?') {
m++;
scanf ("%d%d", &a[i], &b[i]);
v.push_back (make_pair (a[i] - b[i], i));
} else d += int (w[i] == '(') - int (w[i] == ')');
int A = (m - d) / 2, B = (m + d) / 2;
if ((m + d) % 2 || A < 0 || B < 0) {
printf ("-1\n");
return 0;
}
sort (v.begin (), v.end ());
last = -1;
if (go (A, B)) {
printf ("-1\n");
return 0;
}
long long ans = 0;
for (int i = 0; i < v.size (); i++) {
int j = v[i].second;
// printf ("%s %s %d %I64d\n", ww, w, last, ans);
if (ww[j] == '(') {
ans += a[j];
w[j] = '(';
if (last == j) {
last--;
while (last >= 0 && (w[last] != '?' || ww[last] == ')')) last--;
}
} else
if (ww[j] == ')') {
if (check (j - 1)) {
ans += a[j];
w[j] = '(';
} else {
ans += b[j];
w[j] = ')';
}
}
}
if (go (0, 0)) {
printf ("-1\n");
return 0;
}
printf ("%lld\n%s\n", ans, w);
return 0;
}
原写于1月30日,为保护上课隐私,设此密码。

浙公网安备 33010602011771号