题目大意: 有 n n n 张卡片,给出他们正反面的数字,求最少翻转几张卡片,使得排序后,从第一张卡片到最后一张卡片,正面数字递增,反面数字递减。
题解
个人感觉是很优美的一个题,可惜博主脑子笨赛时并不会做。
考虑最后排完序数字应该是什么样的,不难想象, 1 1 1 ~ n n n 这些数字一定分成了两个递增子序列,一部分在前 k k k 张卡的正面,一部分逆序出现在后 n − k n-k n−k 张卡的背面。注意到这样是不可能存在一张卡拥有两个 ≤ n \leq n ≤n 的数字的,这是第一个判无解的条件。
对于 i ≤ n i\leq n i≤n,令 f ( i ) f(i) f(i) 表示与 i i i 在同一张牌的另一个数字。假如我们将后 n − k n-k n−k 张牌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)\} minj≤i{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{j≤x}{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(y−1) 的位置,那么 [ 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;
}