codeforces 1503D Flip the Cards 题解

题目传送门

题目大意: n n n 张卡片,给出他们正反面的数字,求最少翻转几张卡片,使得排序后,从第一张卡片到最后一张卡片,正面数字递增,反面数字递减。

题解

个人感觉是很优美的一个题,可惜博主脑子笨赛时并不会做。

考虑最后排完序数字应该是什么样的,不难想象, 1 1 1 ~ n n n 这些数字一定分成了两个递增子序列,一部分在前 k k k 张卡的正面,一部分逆序出现在后 n − k n-k nk 张卡的背面。注意到这样是不可能存在一张卡拥有两个 ≤ n \leq n n 的数字的,这是第一个判无解的条件。

对于 i ≤ n i\leq n in,令 f ( i ) f(i) f(i) 表示与 i i i 在同一张牌的另一个数字。假如我们将后 n − k n-k nk 张牌reverse一下然后翻转,那么正面就是 1 1 1 ~ n n n 分出来的两个递增子序列,背面自然是 f ( 1 ) f(1) f(1) ~ f ( n ) f(n) f(n) 分出来的两个递减子序列。换言之,接下来的问题完全等价于将 f ( 1 ) f(1) f(1) ~ f ( n ) f(n) f(n) 划分成两个递减子序列并且使代价最小。

不难想到,若 min ⁡ j ≤ i { f ( j ) } > max ⁡ j > i { f ( j ) } \min_{j\leq i}\{f(j)\}>\max_{j>i}\{f(j)\} minji{f(j)}>maxj>i{f(j)},那么就可以在 i i i i + 1 i+1 i+1 间放一个隔板,若干个隔板会将这个问题划分成为互不相干的子问题,最后将每个子问题答案相加就是答案。

这样一划分,每个子问题就会有一个很好的性质:将一个子问题内的子段划分成两个下降子序列的划分方案唯一(假如有解)。

证明也很简单,假设一个子问题中第一个元素在 x x x 位置,由于其中没有任何隔板,所以 min ⁡ { j ≤ x } { f ( j ) } = f ( x ) ≤ max ⁡ j > i { f ( j ) } \min\{j\leq x\}\{f(j)\}=f(x)\leq \max_{j>i}\{f(j)\} min{jx}{f(j)}=f(x)maxj>i{f(j)},假设 y y y 为最左边的且满足 f ( x ) < f ( y ) f(x)<f(y) f(x)<f(y) 的位置,那么 [ x , y ) [x,y) [x,y) 这一段一定是一个下降子段,否则这个序列就至少要被划分成 3 3 3 个下降子序列。然后对于 y y y 后面的位置,设 z z z 为最右边的且满足 ∀ j ∈ [ y , z ] , f ( j ) > f ( y − 1 ) \forall j\in [y,z],f(j)>f(y-1) j[y,z],f(j)>f(y1) 的位置,那么 [ y , z ] [y,z] [y,z] 一定形成了一个下降子序列,从 z + 1 z+1 z+1 开始往后就是新的子问题了。不难发现两个下降子序列的划分都是完全固定的。(注意这是在有解的情况下讨论的)

于是我们维护两个下降子序列,每次贪心地放,假如存在元素放不了那就一定是无解。然后对于每个子问题,看看两个子序列在最终的答案中哪个放前面更优,取 min ⁡ \min min 即可。

代码如下:

#include <bits/stdc++.h>
using namespace std;

int main()
{
	int n;cin>>n;
	vector<int> a(n+1),c(n+1);
	for(int i=0,x,y;i<n;i++){
		cin>>x>>y;
		if(x>y)swap(x,y),c[x]=1;
		if(x>n)return puts("-1"),0;
		a[x]=y;
	}
	vector<int> suf(n+2),s1,s2;
	for(int i=n;i>=1;i--)suf[i]=max(a[i],suf[i+1]);
	int c1=0,c2=0,pre=1e9,ans=0;
	for(int i=1;i<=n;i++){
		if(!s1.size()||s1.back()>a[i])
			s1.push_back(a[i]),c1+=c[i];
		else if(!s2.size()||s2.back()>a[i])
			s2.push_back(a[i]),c2+=c[i];
		else return puts("-1"),0;
		pre=min(pre,a[i]);if(pre>suf[i+1]){
			ans+=min(c1+s2.size()-c2,c2+s1.size()-c1);
			s1.clear();s2.clear();c1=c2=0;
		}
	}
	cout<<ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值