有 \(n\) 个人参加考试,第 \(i\) 个人说,有 \(a_i\) 个人分数比他高,\(b_i\) 个人分数比他低。求至少有多少人说了假话。
Solution
设 \(l_i=a_i+1, r_i=n-b_i\),则假如第 \(i\) 个人说了真话,那么与第 \(i\) 个人分数相同的所有人的名次区间为 \([l_i,r_i]\)
- 如果 \(l_i>r_i\),那么这个人直接删去
- 如果 \((l_i,r_i)\) 相同的人超过了 \(r_i-l_i+1\) 个,就将多出来的人删去
在余下的区间中,每个区间设定一个权值 \(v\) 表示它的重数,现在我们就是要选出若干个不相交的区间使得他们的权值和最大,暴力 dp 即可
考虑将所有区间先按照右端点排序,依次扫描,设 \(f[i]\) 为扫描到第 \(i\) 个区间的最优解,于是我们需要二分找到一个最大的 \(k\) 使得 \(r_k<l_i\),则 \(f[i]=\max(f[i-1],f[k]+v_i)\)
另一种方法是,设 \(f[i]\) 为扫描到 \(i\) 位置时的最优解,那么仍然按照 \(r\) 升序枚举所有区间来转移,转移方程为
\[f[r_i]=\max(f[r_i], f[l_i-1]+v_i) \\
f[i]=\max(f[i],f[i-1])
\]
频繁打错变量名……
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
struct range {
int l,r,v;
bool operator < (const range &b) {
if(r == b.r) return l < b.l;
else return r < b.r;
}
} s[N],x[N];
int n,a[N],b[N],f[N],ind;
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++) {
cin>>a[i]>>b[i];
s[i].l=a[i]+1;
s[i].r=n-b[i];
}
sort(s+1,s+n+1);
for(int i=1;i<=n;i++) {
if(s[i].l>s[i].r) continue;
if(x[ind].l!=s[i].l || x[ind].r!=s[i].r) {
x[++ind]=s[i];
}
x[ind].v++;
}
for(int i=1;i<=ind;i++) {
x[i].v=min(x[i].v,x[i].r-x[i].l+1);
for(int j=x[i-1].r+1;j<=x[i].r;j++) f[j]=max(f[j],f[j-1]);
f[x[i].r]=max(f[x[i].r],f[x[i].l-1]+x[i].v);
}
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,f[i]);
cout<<n-ans;
}