CodeForces-1461F Mathematical Expression 题解

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;
}