\(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;
}

相关文章:

  • 2022-02-28
  • 2021-06-28
  • 2021-07-26
  • 2021-09-15
  • 2022-01-21
猜你喜欢
  • 2021-07-10
  • 2021-05-28
  • 2021-10-17
  • 2021-11-19
  • 2021-05-22
  • 2022-03-07
相关资源
相似解决方案