P9744 「KDOI-06-S」消除序列

P9744

因为给定的 $1$ 的个数是有限的,所以一定是枚举 $p_i$ 到 $p_{i+1}$ 的段进行更新。发现操作一最多只会执行一次,枚举在哪里执行操作一,然后对答案进行更新。因为 $p_i$ 前面的 $1$ 不管怎么都需要清 $0$,这部分代价可以单独计算。但是有可能是在 $j\in[p_i,p_{i+1})$ 进行的操作,所以还需要知道段内从那里分开比较优。

有一种简单解决方法就是直接预处理出 dp 数组 $g$ 表示前 $i$ 个数全部清零的最小代价,转移显然:$g_i=\min\{g_{i-1}+b_i,a_i\}$。这时候发现 $g$ 是段内所有点的取值的 $\min$。

但是赛时没有想到这个方法,用了一个麻烦一点的。考虑当 $i$ 是最优的时候,一定满足 $\arg \min_i a_i+\sum\limits_{j=i+1}^{r}b_i$。把后面做一个前缀和可以表示为 $a_i+s_r-s_i$ 最小,提出 $s_r$ 后就是求区间极值,随便用数据结构维护一下即可。

注意有可能最有决策在 $[p_m,n]$ 的范围内,加一个 $p_{m+1}=n+1$ 即可。注意 ST 表一开始会找到 $0$,所以预处理的 log2 数组要开到 $n+1$。

第二种实现的时空复杂度均为 $\mathcal{O}(n\log n+m)$。

代码:

const int N=5e5+10,inf=1e9;const ll INF=1e18;
int n,m,Q;
int a[N],b[N],c[N],q[N],log_2[N];
ll st[20][N],s[N];
ll gmin(int l,int r){int k=log_2[r-l+1];return min(st[k][l],st[k][r-(1<<k)+1]);}
ll ans[N];
void solve(){
    q[0]=0;ll val1=0,res=INF,del=0;q[++m]=n+1;
    for(int i=1;i<=m;i++)del+=b[q[i]];
    for(int i=1;i<=m;val1+=c[q[i]],del-=b[q[i]],i++){
        int l=q[i-1],r=q[i]-1;
        res=min(res,gmin(l,r)+s[r]+val1+(s[n]-s[r]-del));
    }
    printf("%lld\n",res);
}
int main(){
    for(int i=0;i<20;i++)for(int j=0;j<N;j++)st[i][j]=inf;st[0][0]=0;
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)b[i]=read(),s[i]=s[i-1]+b[i],st[0][i]=a[i]-s[i];
    for(int i=1;i<=n;i++)c[i]=read();
    for(int i=2;i<=n+1;i++)log_2[i]=log_2[i>>1]+1;
    for(int i=1;i<20;i++)for(int j=0;j+(1<<i)-1<=n;j++)st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
    Q=read();
    for(int qi=1;qi<=Q;qi++){
        m=read();
        for(int i=1;i<=m;i++)q[i]=read();
        solve();
    }
    return 0;
}
posted @ 2023-10-18 09:02  Pengzt  阅读(5)  评论(0编辑  收藏  举报  来源