题意:
给出一个后缀表达式,仅包含 和 , 表示数字, 表示运算符。每次可以交换表达式中的两个字符位置,也可以在任意位置插入一个 或一个 ,要求操作完后使得原表达式变成一个合法的后缀表达式,求最少操作次数。
思路:
首先我们需要把题意理顺,怎样的表达式叫做一个合法的表达式。即对于表达式中每一个 号位置,其前缀 的和大于前缀 的和,且表达式的最后一个位置前缀 的和等于前缀 的和减 。
我们令 为 ,令 为 ,则对于每一个 号位置,满足前缀和大于 ,且整个序列和等于 。
首先我们先计算出最少需要加上多少个 ,或者加上多少个 ,如果需要加 ,则放到末尾。如果需要加上 ,则放到开头。放好需要的 和 之和,可以满足整个序列的和为 。
然后我们来考虑整个序列中不合法的位置,即符号为 ,且前缀和小于等于 。我们令这个位置变得合法,只有两种操作,一种是在这个位置之前放 ,然后在末尾添上一个 ;或者交换这个位置和后面的那个位置。然后我们会发现在这个序列中一定有一个最小值和次小值,如果最小值和次小值都不满足要求,则我们需要至少交换两次,或者在开头位置加上 ,末尾加上 ,则最小和次小都会 。
考虑到这个地方之后,我们可以发现要想让操作数最少,我们需要先通过在序列首加 使得次小值合法,然后再通过不断交换最小值和其相邻字符,令最小值也变法,这种策略就可以达到操作数最少的目的。
总结:
这是一个中等难度的贪心问题,比赛时有一个地方考虑错了,导致贪心失败,最后也没有做出来,还是较为懊悔的。
感觉自己在贪心问题上还是比较欠缺的。我觉得主要的原因还是在于一是贪心题做的少了,二是在于不会证明自己的贪心策略,而这主要也在于贪心策略比较难以求证。
如果加强自己的贪心能力呢?目前的策略就是多做一些贪心问题,多总结一些贪心思路,还是在摸索的一个过程,到底可不可行还要走一步看一步。
今天这道贪心问题的一个小得到就是关于序列字符问题,能转成数字就转成数字,因为一般人都对数字更加敏感。
代码:
#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 3000000+10;
const db EPS = 1e-9;
using namespace std;
char s[N];
int sum[N];
int main()
{
int _; scanf("%d",&_);
rep(Ca,1,_){
scanf("%s",s+1);
int s1 = 0, s2 = 0;
int len = strlen(s+1), ans = 0, base = 0;
rep(i,1,len)
if(s[i] == 'a') s1++;
else s2++;
if(s1 > s2) ans += s1-1-s2;
else ans += s2+1-s1, base += s2+1-s1;
sum[0] = base;
int m1 = 1, m2 = 1;
rep(i,1,len){
sum[i] = sum[i-1]+(s[i]=='a'?1:-1);
if(s[i] == 'a') continue;
if(sum[i] < m1) m2 = m1, m1 = sum[i];
else if(sum[i] < m2) m2 = sum[i];
}
if(m2 <= 0 && m1 <= 0)
ans = ans+2*(1-m2)+(m2-m1);
else if(m1 <= 0)
ans = ans+1-m1;
printf("Case %d: %d\n",Ca,ans);
}
return 0;
}