jerry[unLOCK]
Online Judge:20191014 长沙市一中 CSP模拟赛#day2
Label:思维题,贪心,区间Dp
题目描述
众所周知,Jerry 鼠是一只非常聪明的老鼠。
Jerry 聪明到它可以计算64 位有符号整形数字的加减法。
现在,Jerry 写下了一个只由非负整数和加减号组成的算式。它想给这个算式添加合法的括号,使得算式的结果最大。这里加减法的运算优先级相同,和我们在日常生活中接触到的一样,当没有括号时,先算左边的,再算右边的。
比如,算式((1+2)+3-(4-5))+6
是合法的,但是)1+2(
和(-)1+2
以及-(1)+2
都是不合法的。
输入
输入文件为jerry.in。
第一行一个整数T,代表数据组数。
接下来,共有T 组数据,每组的格式如下:
第一行一个整数n,代表数字的个数。
接下来一行共\(2n-1\) 个符号或非负整数,组成一个由空格隔开的算式。
输出
输出文件为jerry.out。
一行一个整数,代表添加括号后,算式最大的可能结果。
样例
Input#1
1
3
5 - 1 - 3
Output#1
7
Input#2
2
4
1 - 1 - 1 - 3
4
1 - 1 - 1 + 3
Output#2
4
4
对于第一个样例:5-(1-3)=7
;
第二个样例:1-(1-1-3)=4
,1-(1-(1+3))=4
Hint
对于50%数据,\(n<=100,T<=100\)。
对于70%数据,\(n<=1000,T<=100\)。
对于100%数据,\(∑n<=2*10^5\)。
对于全部数据,\(2<=n<=10^5,∑n<=2*10^5\),算式中的数字在\([0,10^9]\)内。
题解
50pts
很容易想到\(O(N^3)\)的区间Dp,维护两个\(mi[l][r],ma[l][r]\),表示在只考虑区间\((l,r)\)的最小/大值。
转移如下:
inline void solve(){
n=read();
char s[2];
for(register int i=1;i<n;++i){
a[i]=read();
scanf("%s",s);
op[i]=(s[0]=='+'?0:1);
}
a[n]=read();
for(register int i=0;i<=n;++i)ma[i][i]=mi[i][i]=a[i];
for(register int i=2;i<=n;i++){
for(register int l=1;l+i-1<=n;++l){
int r=l+i-1;
ll now=a[l],Mi=Inf,Ma=-Inf;
for(register int k=l;k<r;++k){
if(op[k]){
MA(Ma,ma[l][k]-mi[k+1][r]);
MI(Mi,mi[l][k]-ma[k+1][r]);
}
else{
MA(Ma,ma[l][k]+ma[k+1][r]);
MI(Mi,mi[l][k]+mi[k+1][r]);
}
}
ma[l][r]=Ma,mi[l][r]=Mi;
}
}
printf("%lld\n",ma[1][n]);
}
70pts
上面那个太无脑了。仔细观察一下,尝试简化问题模型。
对于区间中的某个数字\(a[i]\),假如i前面有\(j\)个左括号\((\)还没匹配,那么,可以根据括号的奇偶性以及i前面的符号,确定此时\(a[i]\)计入总值的正负性。
于是定义状态\(f[i][j]\)表示,现在已经决策完了前i个数字的括号摆放,且有j个左括号还没被匹配,前面i个元素计入总值的和最大为多少。
代码如下,只用枚举\(i,j\),所以时间复杂度为\(O(N^2)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll Inf=1e12;
const int N=1005;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
int n,a[N],op[N];//op=0:+, op=1:-;
ll f[N][N];
inline void MA(ll &x,ll y){if(x<y)x=y;}
inline void MI(ll &x,ll y){if(x>y)x=y;}
inline void solve(){
n=read();
char s[2];
for(register int i=1;i<n;++i){
a[i]=read();
scanf("%s",s);
op[i]=(s[0]=='+'?0:1);
}
a[n]=read();
for(register int i=1;i<=n;i++)for(register int j=0;j<=i;j++)f[i][j]=-Inf;
f[1][0]=a[1];
for(register int i=2;i<=n;i++){//当前是第i个数
for(register int j=0;j<i;j++){//前面还未匹配的括号数
bool isfu=op[i-1],tmp=j&1;
int g=1;
//什么括号都不加
isfu^=tmp;if(isfu)g=-1;
MA(f[i][j],f[i-1][j]+g*a[i]);
//i)
if(j>0)MA(f[i][j-1],f[i-1][j]+g*a[i]);
//(i
if(op[i-1]){
if(j&1)g=1;
else g=-1;
MA(f[i][j+1],f[i-1][j]+g*a[i]);
}
}
}
//for(register int i=1;i<=n;i++)for(register int j=0;j<i;j++){
// cout<<"考虑前"<<i<<"个数字 "<<"现在还有"<<j<<"个左括号未匹配 val="<<f[i][j]<<endl;
//}
printf("%lld\n",f[n][0]);
}
int main(){
freopen("jerry.in","r",stdin);
freopen("jerry.out","w",stdout);
int T=read();
while(T--)solve();
}
100pts
根据70分的模型,仔细深入一想,其实不必枚举所有\(j\),因为每个数的取值只跟未匹配括号个数的奇偶性有关,而跟具体个数无关。
也就是说,在最优解中,括号的嵌套层数不会超过2。
所以只需枚举\(i\),原本的\(j\)那一层变成了常数,相当于\(O(N)\)。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll Inf=1e15;
const int N=2e5+10;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
int n,a[N],op[N];
ll f[N][3];
inline void MA(ll &x,ll y){if(x<y)x=y;}
inline void solve(){
n=read();
char s[2];
for(register int i=1;i<n;++i){
a[i]=read();
scanf("%s",s);
op[i]=(s[0]=='+'?0:1);
}
a[n]=read();
for(register int i=1;i<=n;i++)f[i][0]=f[i][1]=f[i][2]=-Inf;
f[1][0]=a[1];
for(register int i=2;i<=n;i++){//当前是第i个数
for(register int j=0;j<=2;j++){//前面还未匹配的括号数
bool isfu=op[i-1],tmp=j&1;
int g=1;
//什么括号都不加
isfu^=tmp;if(isfu)g=-1;
MA(f[i][j],f[i-1][j]+g*a[i]);
//i)
if(j>0)MA(f[i][j-1],f[i-1][j]+g*a[i]);
//(i
if(j<2&&op[i-1]){
if(j&1)g=1;
else g=-1;
MA(f[i][j+1],f[i-1][j]+g*a[i]);
}
}
}
printf("%lld\n",f[n][0]);
}
int main(){
freopen("jerry.in","r",stdin);
freopen("jerry.out","w",stdout);
int T=read();
while(T--)solve();
}