hydd的博客

博客

联合省选2022-序列变换(D2T2)

2022-04-19 14:43:09 By hydd

$x=0,y=0$ 答案显然为 $0$。

先把括号序列建树。和WC那题区别在于,那题建的是二叉树,这题建的是普通的多叉树(二叉树其实就是普通多叉转二叉得到的)。

左括号的权值即要求边上带权。操作1就是把相邻的两个子树,右边的并到左边,中间加一条新的边,权值为右边子树原先和父亲连边的权值。

操作2就是交换相邻两个子树的顺序。

最终要求没有一个点有两个儿子,也就是结果形如一条链,对应括号序列 ((((...))))

如果括号序列本身就不需要操作,直接特判,答案为 $0$。

容易发现最优策略是按照深度从小到大依次合并,因为两个子树并起来后再做会比两个子树先分别做再并会更优。

做完前 $i-1$ 层会变成一条链,第 $i$ 层的点只剩下一个,往下连了原先第 $i$ 层的所有边,且新增了一些边(如上图红色的 $c$ 边)的权值产生影响。故树的形态无关紧要,只需要考虑第 $i$ 层所有往下的边的权值。

现在操作可以简化为,每次在当前层选取任意两条往下一层的边,把其中一条丢到下一层,并计算新增代价,直到只剩下一条。

一方面要这一层新增的代价尽量小,另一方面要往下一层丢的权值尽量小。

丢的权值尽量小,每一层只能留下一条,所以丢下去的尽量小就是把最大值留在当前层。

简单情况

当 $x=0,y=1$ 时,假设保留边 $a$,唯一策略是每次选择 $a$ 边和非 $a$ 边合并保留 $a$ 边。 除了保留的边外其它边恰好贡献了一次,设保留边权值为 $w$,所有边之和为 $s$,代价为 $s-w$。 $s$ 固定,故 $w$ 越大越好,所以把最大的留在当前层,同时满足了丢的权值尽量小的条件。

当 $x=1,y=1$ 时,此时每次合并代价与保留谁无关(都选与最小边合并)。 设边数量为 $t$,保留最小边权值为 $m$,所有边之和为 $s$,代价为 $s+(t-2)m$。 还是可以把最大的留在当前层,也满足了丢的权值尽量小的条件。

重头戏

当 $x=1,y=0$ 时,还是先与最小值合并,保留的不是最小值最后把保留值和最小值合并丢到下一层。 设边数量为 $t$,保留最小边权值为 $m$,代价为 $w+(t-2)m$。 此时这种情况新增代价最小的并不是保留最大值而是保留最小值,两个不一致,不能直接贪心。

仔细研究一下,首先每一层的 $t$ 是无论保留什么都不变的,设前 $i$ 层共有 $s_i$ 个值,那么第 $i$ 层的 $t$ 就是 $s_i-i+1$。而树是不能出现断层的,故在 $i$ 不超过树高之前 $t$ 是不降的,超过树高之后 $t$ 就每次 $-1$。

还是拆成两部分,一部分是每一层的 $m$ 尽量小,另一个是 $w$ 尽量小。

最开头的一段 $t=1$ 的一定没用,每一个被保留的都会算一次代价,所以你要 $w$ 之和尽量小也就是最后的一层(也就是合并结束时的那层 $t=1$)的 $w$ 值尽量大。

倒数第二层(合并结束前的那层 $t=2$)的策略也显然是保留最小值,把较大的 $w$ 扔下去。

当 $t\geq 3$ 开始你就可以每次保留非最小值和最大值的一个值,这样即保证了每次能传下去最小值使得 $m$ 尽量小,又保证了最后保留的 $w$ 能是 $t\geq 3$ 开始的最大值。

关键就在于开头 $t=1$ 后的那段 $t=2$,这一段的代价和保留的值决定了最终的代价。如果这一段保留的值比较小,那么它可以变为后面几层的 $m$;如果保留的值比 $t\geq 3$ 开始的最大值还要大,那么可以变成最后保留的 $w$。如果两个都不是的一定不优。

如果你保留的数不比之后的最大值还要大,那么你可以减小保留的值,从而缩小后面几层的 $m$;如果比最大值还要大,那么你一定不会对后面几层的 $m$ 有影响,这样不如继续加大保留的值使得最后剩下的 $w$ 尽量大。

所以对于开头的这段 $t=2$,你只需要保留最小值/最大值即可。两个都算一下取个 $\min$ 就是答案。

这个两段分开考虑还是不好写,可以直接考虑把策略变成每次保留次大值/次小值,这样就不需要把前面的 $t=2$ 和之后 $t\geq 3$ 的分开写了。最后的一个 $t=2$ 直接手动处理一下,也就是把两个数较小值加进答案即可。

代码(不知道对不对):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
int n,x,y,cnt,v[810000],p[810000];
int num[810000],nxt[810000],z[810000];
string s; multiset<int,greater<int> > q;
vector<int> vec[810000];
void solve(int l,int r,int dep){
    while (l<r){
        vec[dep].push_back(v[p[l]]);
        solve(l+1,nxt[l]-1,dep+1);
        l=nxt[l]+1;
    }
}
void build(){
    int sum=0,tot=0;
    for (int i=1;i<=n+n;i++)
        if (s[i-1]=='(') sum++,num[sum]=i,p[i]=++tot;
        else nxt[num[sum]]=i,sum--;    
    solve(1,n+n,1);
}
ll solve01(int l,int r){
    ll sum=0,ans=0; q.clear();
    for (int i=l;i<=r;i++){
        for (int v:vec[i]) q.insert(v),sum+=v;
        sum-=*q.begin(); q.erase(q.begin()); ans+=sum;
    }
    return ans;
}
ll solve11(int l,int r){
    ll sum=0,ans=0; q.clear();
    for (int i=l;i<=r;i++){
        for (int v:vec[i]) q.insert(v),sum+=v;
        int mn=*q.rbegin();
        ans+=sum; ans+=(q.size()-2)*mn;
        sum-=*q.begin(); q.erase(q.begin());
    }
    return ans; 
}
ll solve10_min(int l,int r){
    ll sum=0,ans=0; q.clear();
    for (int i=l;i<r;i++){
        for (int v:vec[i]) q.insert(v),sum+=v;
        int c=*++q.begin(),mn=*q.rbegin();
        ans+=c; ans+=(q.size()-2)*mn;
        sum-=c; q.erase(q.find(c));
    }
    for (int v:vec[r]) q.insert(v),sum+=v;
    return ans+*q.rbegin();
}
ll solve10_max(int l,int r){
    ll sum=0,ans=0; q.clear();
    for (int i=l;i<r;i++){
        for (int v:vec[i]) q.insert(v),sum+=v;
        int c=*++q.rbegin(),mn=*q.rbegin();
        ans+=c; ans+=(q.size()-2)*mn;
        sum-=c; q.erase(q.find(c));
    }
    for (int v:vec[r]) q.insert(v),sum+=v;
    return ans+*q.rbegin();
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    cin>>n>>x>>y; cin>>s;
    for (int i=1;i<=n;i++) cin>>v[i];
    build();

    z[1]=vec[1].size(); int pos=0;
    for (int i=2;i<=n+1;i++){
        z[i]=(z[i-1]-1)+vec[i].size();
        if (z[i]==0){ pos=i; break;}
    }
    pos--; assert(z[pos]==1);

    int cur=0;
    for (int i=1;i<pos;i++)
        if (z[i]>1){ cur=i; break;}
    if (!cur){ cout<<"0\n"; return 0;}


    if (x==0&&y==1){ cout<<solve01(cur,pos-1)<<'\n'; return 0;}
    if (x==1&&y==1){ cout<<solve11(cur,pos-1)<<'\n'; return 0;}
    if (x==1&&y==0){ cout<<min(solve10_min(cur,pos-1),solve10_max(cur,pos-1))<<'\n'; return 0;}
    cout<<"0\n";
    return 0;
}

我这写的什么离谱题解。。。

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。