[省选联考 2022] 序列变换
一、题目
二、解法
话说省选如果能做出这道题就圆满了,但是只拿 \(8\) 分确实应该反思一下。树形结构这东西我甚至专门开了个分类,而且思维技巧中也有总结,这题的转化也比较明显,为什么就是想不到呢?总结的技巧还是多主动去使用吧,这题要是往图论方向想我将绝杀。
题目转化:如果我们建出括号序列的括号树,那么操作一和操作二的效果等价于,对于某个节点 \(u\) 的任意两个儿子 \(a,b\),我们把 \(b\) 的所有儿子边断开,然后把 \(b\) 和 \(b\) 的儿子都接到 \(a\) 上。
第一个 \(\tt observation\):我们按照树从上往下,分层操作的顺序最优。这是因为总操作次数是一定的,而你先操作上面会留给下面更多的选择,这是可以让方案代价变得更优的。
那么此时问题就被简化了许多,开始分类讨论 \(x,y\) 的四种情况:
-
若 \(x=y=0\),不需要代价。
-
若 \(x=0,y=1\),那么代价是当前层的权值和\(-\)当前层留下来的权值,显然留下最大值是最优的。
-
若 \(x=y=1\),考虑权值是 最小值 \(\times(sz-2)\) \(+\) 当前层的权值和( \(sz\) 的含义是考虑到当前层的子树大小,注意不是初始的子树大小),显然留下最大值是最优的。
有人说上面的部分是蓝题,可以拿到 \(36\) 分的高分了。 -
若 \(x=1,y=0\),考虑权值是 最小值 \(\times(sz-2)\) \(+\) 当前层留下来的权值,好像就很难贪心了。
我们从整体的角度来思考权值的含义。可以将权值拆分,初始权值是所有点的权值和,那么我们就要最小化 最小值 \(\times(sz-2)\),直接下传最小值是错误的,因为最后一层要产生负贡献。这里又要以 \(sz\) 分类讨论了:
- \(sz=1\),不需要操作。
- \(sz>2\),第二个关键的 \(\tt observation\) 是:我们把最小值和最大值都下传是最优的。因为这样既可以最小化每一层的代价,又可以保证最后一层的负贡献。
- \(sz=2\),难做。
但是我们又发现 \(sz=2\) 是一个连续段(先不考虑最后的单点),这个连续段自身是不会产生任何贡献的(因为 \(sz-2=0\)),那么我们可以下放最大值来优化最后一层的负贡献,或者是下放最小值来优化其他层的 最小值 \(\times(sz-2)\),所以我们只需要模拟这两种情况,然后取最小值即可。至于最后 \(sz=2\) 的单点,直接下放最大值就可以了。
时间复杂度 \(O(n\log n)\),实现基本没有难度。
三、总结
主动去发掘问题的图论结构,可以做出第一步转化!
贪心的关键是观察权值的特点,还可以用拆分法使得贪心的使用更加直观。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <set>
using namespace std;
const int M = 400005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,x,y,tp,ans,sum,q[M],v[M],sz[M],mx[M],mn[M];
vector<int> b[M];multiset<int> s;char a[M<<1];
signed main()
{
n=read();x=read();y=read();scanf("%s",a+1);
for(int i=1;i<=n;i++) v[i]=read();
for(int i=1,j=0;i<=n<<1;i++)
{
if(a[i]=='(') q[++tp]=++j;
else b[tp].push_back(v[q[tp]]),tp--;
}
if(x==0 && y==1)
{
for(int i=1;i<n;i++)
{
for(int x:b[i]) s.insert(x),sum+=x;
sum-=*s.rbegin();ans+=sum;
s.erase(--s.end());
}
}
if(x==1 && y==1)
{
for(int i=1;i<n;i++)
{
for(int x:b[i]) s.insert(x),sum+=x;
ans+=((int)s.size()-2)*(*s.begin())+sum;
sum-=*s.rbegin();s.erase(--s.end());
}
}
if(x==1 && y==0)
{
memset(mn,0x3f,sizeof mn);sz[0]=1;
for(int i=1;i<n;i++)
sz[i]=b[i].size()+sz[i-1]-1;
int u=1,v=1;
while(u<n && sz[u]==1) u++;v=u;
while(v<n && sz[v]==2) v++;
for(int i=u;i<n;i++)
{
if(i!=v) mn[i]=mn[i-1],mx[i]=mx[i-1];
for(int x:b[i]) sum+=x,
mn[i]=min(mn[i],x),
mx[i]=max(mx[i],x);
}
int r1=sum-mx[n-1],r2=sum-max(mx[n-1],mx[v-1]);
//1 : download the min ; 2 : download the max
for(int i=v;i<n;i++)
r1+=(sz[i]-2)*min(mn[i],mn[v-1]),
r2+=(sz[i]-2)*mn[i];
ans=min(r1,r2);
}
printf("%lld\n",ans);
}