[省选联考 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);
}
posted @ 2022-04-24 20:33  C202044zxy  阅读(303)  评论(0编辑  收藏  举报