P2519 [HAOI2011]problem a
有 \(n\) 个人考试,第 \(i\) 个人说有 \(a_i\) 个人分数高于他,\(b_i\) 个低于他,求最少有多少个人在说谎。
很有意思的 DP 题,一开始以为是 2-sat 建模,然后发现不太行...。
转化一下他们说的话:“第 \(a_i+1\sim n-b_i\) 号人的分数和我一样”。
那么一定说谎的条件:
- \(a_i+1>n-b_i\)。
- 有超过区间总长的人都认为 \(a_i+1\sim n-b_i\)。
这些可以一开始直接排除,之后相同区间的人都可以合并到这个区间内,并作为这个区间的价值。
显然,相交的区间不能选择,例如有区间 \([a,b],[b,c]\),这说明 \(a=b>c,a>b=c\) 同时满足,这显然不可能。
容斥一下,问题变为,在序列内选择一些不相交的区间,使得总价值最大,这是比较经典的 DP 模型。
设 \(f(i)\) 表示前 \(i\) 区间能取得的最大价值,然后每次二分找到前面第一个不相交的区间即可。(似乎还可以 \(O(n)\))
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define X first
#define Y second
#define MP make_pair
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, t, f[N]; PII a[N];
struct Node{int l, r, v;} p[N];
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
bool cmp(Node a, Node b){return a.r == b.r ? a.l < b.l : a.r < b.r;}
int Get(int limit, int l, int r){
while(l < r){
int mid = (l + r + 1) >> 1;
if(p[mid].r < limit) l = mid;
else r = mid - 1;
}
return f[l];
}
int main(){
n = read();
for(int i = 1; i <= n; i ++){
int l = read(), r = read();
a[i] = MP(l + 1, n - r);
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i ++){
if(a[i].X > a[i].Y) continue;
if(i == 1 || a[i] != a[i - 1])
p[++ t] = (Node){a[i].X, a[i].Y, 1};
else
p[t].v ++, p[t].v = min(p[t].v, a[i].Y - a[i].X + 1);
}
sort(p + 1, p + t + 1, cmp);
p[0] = (Node){0, 0, 0};
for(int i = 1; i <= t; i ++){
int val = Get(p[i].l, 0, i - 1);
f[i] = max(f[i - 1], val + p[i].v);
}
printf("%d\n", n - f[t]);
return 0;
}