清北学堂例题 LUOGU2519 【HAOI2011】PROBLEM A

题目描述

一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低。”问最少有几个人没有说真话(可能有相同的分数)

输入格式

第一行一个整数n,接下来n行每行两个整数,第i+1行的两个整数分别代表ai、bi

输出格式

一个整数,表示最少有几个人说谎

输入输出样例

输入 #1
3
2 0
0 2
2 2
输出 #1
1

说明/提示

100%的数据满足: 1≤n≤100000 0≤ai、bi≤n

思路:

转变题意:题目中的 ai bi 表示从1+ai 到 n-bi排名区间内的同学的分数相同,且与不属于该区间的同学分数一定不同。

考虑补集思想:最少说假话人数=总人数-最多说真话人数

而又显然:如果两个区间相交但不重合,则发生冲突。

比如一个人说第三名和第四名的成绩相同,另一个说第四名和第五名成绩相同,则必然有一个人说了假话

考虑给区间赋上权值:一个区间的权值=本质相同的区间总数,且最大为区间长度。

比如有五个人都说自己的排名是第三到第五名,则至多三个同学说真话。

于是题目就变成了一个区间覆盖问题qvq 求权值最大且互不相交的区间覆盖

DP即可。注意和【今年暑假不AC】不同,由于区间带权,不能直接贪心(爆零警告)

先以右端点为第一关键字,左端点为第二关键字排序。

定义DP [ i ] 表示右端点覆盖到 i 的合法方式最大权值。

dp[ r [ i ] ] = max (dp [ l [ i ] ] + w[ i ],dp [ r [ i ] ] ) ;

dp[ i ] = max ( dp [ i - 1 ] , dp [ i ] ) ;

值得一提的是第二个转移必须要在 i 之前的①转移都已完成(也即i<=当前右端点)。注意转移顺序,具体见代码(貌似又写丑了orz)

时间复杂度O(nlogn)

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;

const int maxn=100005;

int dp[maxn];
struct node{
    int l,r,w;
}a[maxn];

inline bool cmp(node a,node b) {
    if (a.r==b.r) return a.l<b.l;
    return a.r<b.r;
}

inline void init(int n) {
    int cnt_l=0,cnt_r=0,cnt_val=0;
    for (int i=1;i<=n;i++) {
        int now_l=a[i].l,now_r=a[i].r;
        if (now_l!=cnt_l || now_r!=cnt_r) {
            cnt_val=0;
            cnt_l=now_l,cnt_r=now_r;
        }
        cnt_val++;
        a[i].w=min(cnt_val,now_r-now_l+1);
    }
}

int main() {
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        int x,y;
        scanf("%d%d",&x,&y);
        a[i].l=x+1,a[i].r=n-y;
    }
    sort(a+1,a+n+1,cmp);
    init(n);
    int cnt=0;
    for (int i=1;i<=n;i++) {
        if (a[i].r<a[i].l) continue;
        if (a[i].r>cnt) {
            for (int j=cnt+1;j<=a[i].r;j++) dp[j]=max(dp[j],dp[j-1]);
            cnt=a[i].r;
        }
        int l=a[i].l,r=a[i].r;
        dp[r]=max(dp[r],dp[l-1]+a[i].w);
    }
    if (n>cnt) for (int i=cnt+1;i<=n;i++) dp[i]=max(dp[i],dp[i-1]);
    cout << n-dp[n];
}
原文地址:https://www.cnblogs.com/YoOXiii/p/11342998.html