[省选联考 2022] 序列变换 题解

神奇贪心。

改变一下操作的形态,发现就是括号树上两个节点 \(a,b\),把 \(b\)\(b\) 的所有儿子挂到 \(a\) 上。

有一个显然的贪心策略是从上到下合并,这样可以在不改变上层的选择的情况下让下层的选择更多。

观察到只有四种情况。开始分讨。

x=0,y=0

憨憨题。

x=0,y=1

每次合并代价是 \(b\) 的代价。那么每一层留下一个最大的就行了。开个 multiset 存权值,算一下就行了。

x=1,y=1

每次合并是 \(a+b\) 的代价。发现只要把除了最大值以外的值合到最小值上,再把最小值合到最大值上就行了。每一层的权值是(个数 \(-2\))乘最小值再加一个所有的和。

上边所有的加起来有 32 分。

x=1,y=0

每次合并是 \(a\) 的代价。考虑以下我们的策略:如果每次把所有的合到最小的(剩下一个节点),然后把最小的合到剩下的节点下面,代价就是(个数 \(-2\))乘最小值再加上剩下的节点的权值。然而容易发现随着向下合并每次都要算上一个节点的代价,那么最后只有一个节点可以不算代价。显然把最大值和最小值都下放到最底层是最优的。

然后考虑一些情况:

  1. 只有一个儿子。没法下放,直接跳过。
  2. 大于两个儿子。下放最大值和最小值。
  3. 只有两个儿子。不是很好搞。

发现只有两个儿子的一定可以形成一段最前面的连续段(跳过 \(1\) 后的)。那么对这些连续段内的元素讨论。发现只有两种情况是最优的:把这一段的最小值放到底层,或者把最大值放到底层。那么模拟两种情况,选个最小的即可。

意外的好写。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int n,x,y,a[400010];
long long ans,sum;
char ch[800010];
multiset<int>s;
vector<int>g[400010];
int top,cnt,stk[400010],size[400010],mx[400010],mn[400010];
int main(){
    scanf("%d%d%d%s",&n,&x,&y,ch+1);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    if(x==0&&y==0){
        puts("0");return 0;
    }
    for(int i=1;i<=(n<<1);i++){
        if(ch[i]=='(')stk[++top]=++cnt;
        else{
            g[top].push_back(a[stk[top]]);top--;
        }
    }
    if(x==0&&y==1){
        for(int i=1;i<n;i++){
            for(int x:g[i])s.insert(x),sum+=x;
            int ret=*prev(s.end());sum-=ret;
            ans+=sum;
            s.erase(prev(s.end()));
        }
    }
    if(x==1&&y==1){
        for(int i=1;i<n;i++){
            for(int x:g[i])s.insert(x),sum+=x;
            int mn=*s.begin();ans+=1ll*mn*(s.size()-2);
            ans+=sum;sum-=*prev(s.end());
            s.erase(prev(s.end()));
        }
    }
    if(x==1&&y==0){
        size[0]=1;
        for(int i=1;i<=n;i++)size[i]=size[i-1]+g[i].size()-1,mn[i]=0x3f3f3f3f;
        int pos1=1,pos2=1;
        while(pos1<=n&&size[pos1]==1)pos1++;
        pos2=pos1;
        while(pos2<=n&&size[pos2]==2)pos2++;
        for(int i=pos1;i<n;i++){
            if(i!=pos2)mn[i]=mn[i-1],mx[i]=mx[i-1];
            for(int x:g[i])mn[i]=min(mn[i],x),mx[i]=max(mx[i],x),sum+=x;
        }
        long long ans1=sum-mx[n-1],ans2=sum-max(mx[pos2-1],mx[n-1]);
        for(int i=pos2;i<n;i++){
            ans1+=1ll*(size[i]-2)*min(mn[pos2-1],mn[i]);
            ans2+=1ll*(size[i]-2)*mn[i];
        }
        ans=min(ans1,ans2);
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2023-02-15 15:23  gtm1514  阅读(20)  评论(0编辑  收藏  举报