Solution
\(s\) 的可能性不多,分类讨论。*、+、-、+-、*- 的情况都是平凡的。对于 *+-,将所有 - 替换为 + 不会使答案变差,因此等价于 *+。
下面重点分析 *+ 的情况,此时,问题等价于将原序列划分为若干个连续子段,最大化各段乘积之和。
1. 化简序列
0 会使所在段乘积直接归零,若想最优,0 应单独成段。于是每个 0 将序列划分为若干互相独立的非零区间。
而 1 作为因子不会改变乘积,但单独成段可以额外贡献 1。于是每个区间两端的 1 都应单独成段。
因此,以下只需解决两端均不小于 2 的子问题。设其长度为 \(m\),元素为 \(b_1,b_2,\ldots,b_m\),满足 \(b_i\ge 1\) 且 \(b_1,b_m\ge 2\)。
2. 全部填 * 的条件
感性理解一下,* 的增长速度远快于 +,分段产生的额外收益主要来自被单独分出的 1。只要 \(\ge 2\) 的数不太少,则全填 * 一定最优。
记
\P=\\prod_{i=1}\^{m}b_i, \\quad q=\\left\|\\{i\\mid b_i\\ge 2\\}\\right\| \\
即 \(q\) 为 \(b\) 序列中 \(\ge 2\) 的数个数。
如果 \(q\ge 2\),考虑任意一种划分。设乘积 \(\ge 2\) 的段共有 \(k\) 个,取值分别为 \(c_1,c_2,\dots,c_k\)。另有 \(m-k\) 个仅由 \(1\) 个 \(1\) 组成的段,它们的总贡献 \(< m\)。
由于 \(1\) 不影响乘积,有
\P=\\prod_{i=1}\^{k}c_i \\
当 \(k\ge 2\) 时,取 \(k=2,c_1=2\),可以使 \(\sum_{i=1}^{k}c_i\) 取最大值 \(2+P/2\)。
因此
\\\sum_{i=1}\^{k}c_i \\le 2+\\frac{P}{2} \\
又由 \(P\ge 2^q\),得到
\\\begin{aligned} \&P-\\sum_{i=1}\^{k}c_i\\ge m \\\\\[8px \Longleftarrow& \frac{P}{2}-2\ge m \\8px \Longleftarrow& P\ge 2m+4 \\8px \Longleftarrow& 2^q\ge 2m+4 \\8px \Longleftarrow& q\ge \lceil \log_2 (2m+4)\rceil \\8px \end{aligned} \]
也就是当 \(q\ge \left\lceil\log_2(2m+4)\right\rceil\)
时,全填 * 一定最优。
3. 对剩余情况 DP
如果上述条件不满足,则 \(q=O(\log m)\)。
可以直接 DP 分段方案,具体实现参见代码,复杂度 \(O(\log^2 m)\)。
4. 复杂度分析
设当前子问题长度为 \(m\),其中 \(\ge 2\) 的元素有 \(q\) 个。
首先,输出就要 \(\Theta(m)\) 的时间。DP 的时间复杂度为 \(\Theta(q^2)=\Theta(\log^2m)<\Theta(m)\)。因此每个子问题为 \(\Theta(m)\)。
故总时间复杂度 \(\Theta(n)\)。
Code
cpp
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i(a);i<b;++i)
#define per(i,a,b) for(int i(a);i>b;--i)
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define pert(i,a,b) for(int i(a);i>=b;--i)
#define int long long
#define me(x,y) memset(x,y,sizeof(x))
using namespace std;
constexpr int N=1e5+1;
int a[N],n;
string s;
namespace MulSub{
void solve(){
if(!a[1]){
cout<<'0';
rept(i,2,n) cout<<(a[i]?'*':'-')<<a[i];
return;
}
int i=2;
cout<<a[1];
while(i<=n&&a[i]) cout<<'*'<<a[i++];
if(i>n) return;
cout<<'-'<<'0',++i;
while(i<=n) cout<<(a[i]?'*':'-')<<a[i++];
}
}
namespace MulAdd{
constexpr int S=19,INF=1e18;
bool flag=true;
int p[S],s[S][S],dp[S],where[S];
inline void submit(char c,int x){
if(flag) flag=false;
else cout<<c;
cout<<x;
}
void sub2(int l,int r){ // 不含0,左右无1的子问题
int cnt=0,m=0;
rept(i,l,r) cnt+=a[i]>1;
if(cnt>S){
submit('+',a[l]);
rept(i,l+1,r) submit('*',a[i]);
return;
}
rept(i,l,r) if(a[i]>1) p[m++]=i; // 找出所有>=2的数并记录位置
me(dp,0);
me(where,0);
// s[i][j]:p[i]~p[j]的乘积
// dp[i]:p[i]~p[m-1]的答案
rep(i,0,m){
s[i][i]=a[p[i]];
rep(j,i+1,m) s[i][j]=s[i][j-1]*a[p[j]];
}
dp[m-1]=a[p[m-1]];
where[m-1]=m-1;
pert(i,m-2,0){
dp[i]=-INF;
rept(j,i,m-2){ // 枚举首个段的右端点
int t=s[i][j]+(p[j+1]-p[j]-1)+dp[j+1];
if(t>dp[i]){
dp[i]=t,where[i]=j;
}
}
if(s[i][m-1]>dp[i]){
dp[i]=s[i][m-1];
where[i]=m-1;
}
}
int i=0;
while(true){
int t=where[i];
submit('+',a[p[i]]);
rept(j,i+1,t){
rep(_,0,p[j]-p[j-1]-1) submit('*',1);
submit('*',a[p[j]]);
}
if(t>=m-1) break;
rep(_,0,p[t+1]-p[t]-1) submit('+',1);
i=t+1;
}
}
void sub1(int l,int r){ // 不含0的子问题
int l2=l,r2=r;
// 去除前后缀1
while(l2<=r&&a[l2]==1) submit('+',1),++l2;
while(r2>=l2&&a[r2]==1) --r2;
if(l2<=r2) sub2(l2,r2);
rep(_,0,r-r2) submit('+',1);
}
void solve(){
int l=0;
rept(i,1,n){
if(a[i]&&!l) l=i;
if(!a[i]){
if(l) sub1(l,i-1),l=0;
submit('+',0);
}
}
if(l) sub1(l,n);
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
cin>>n;
rept(i,1,n) cin>>a[i];
cin>>s;
sort(s.begin(),s.end());
if(s.size()==1){
cout<<a[1];
rept(i,2,n) cout<<s<<a[i];
}else if(s=="+-"){
cout<<a[1];
rept(i,2,n) cout<<'+'<<a[i];
}else if(s=="*-") MulSub::solve();
else MulAdd::solve();
return 0;
}