problem a

 

Description

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

Input

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

Output

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

Sample Input

3

2 0

0 2

2 2

Sample Output

1

HINT

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

 

第一种思路:

     设dp[i]表示在前i名中最多有多少人说真话,则n-dp[n]即为所求。注意,如果有一个人的名次区间为[l, r],那么我们只在dp[r]中考虑他。

状态转移方程:dp[i] = max{dp[j-1]+min(sum[j][i],i-j+1)} 其中sum[j][i]表示名次区间为[j, i]的人数。// min(sum[j][i],i-j+1):如果存在说假话的人,那sum[j][i]有可能大于i-j+1,不能取。

时间复杂度为O(n^2),需要进行优化。

考虑到大多数sum[j][i]为0,为冗余数据,不必枚举,所以我们建立一个数组match[i][j]来表示第j个数k使得sum[k][i]不为0(c++可以使用vector),减少决策的枚举量。

为了方便记录sum[i][match[i][j]],我使用了map(否则空间开不下,且申请内存时间过长),所以总时间复杂度为O(nlogn)

Map用法太神奇了,O r z了!!!

     第二种思路:(自己不会,先粘过来,慢慢领悟……)

其实我们可以很巧妙的把这道题转化成一道线段覆盖的问题,怎么转化呢?

对于每一个描述,我们可以根据他所描述的比他高的和比他矮的人数来构造一条线段,左端点l即为y+1,右端点r为n-x。

当我们转化成线段以后,这一段线段就表示着分数相同的人数,那么显然,只有与这个线段完全重合的线段是符合要求的,对于有交集的线段一定是有一个说谎的,但是对于完全重合的线段,还是有可能出现说谎的情况,因为,当完全重合的线段的数量大于这个线段的长度时,就有num-len个人说谎。

这样我们就可以把这个问题转化成一个线段覆盖的问题了,我们只需要求出有多少重合的线段,那么这个线段的权值就是这个数。然后我们再做带权值的线段覆盖就好了。

至于怎么在O(n+n*log(n))的时间内做出来呢:先按照右端点排序,然后从前向后枚举每一个线段,用f数组记录在当前坐标时能取到的最大值:f[a[i].r]=f[a[i].l-1]+a[i].v;

至于为什么是f[a[i].l-1]呢?因为由于这个问题的特殊性,可能会出现【x,x】这种线段,所以为了避免比较麻烦的问题,就这样处理了。再做的时候还需要一直维护着f数组,我们用一个now变量,一直跟着右端点走,总共用(n)的时间来维护f数组。

最后还有一点,其实这个题可以不用map(因为我不会。。。)

#define MAXN 100010UL
#include<cstdio>
#include<map>
#include<vector>
using namespace std;
int n,dp[MAXN];
map<pair<int,int>,int>op;
vector<int>ex[MAXN];
int MAX(int a,int b){
    return a>b?a:b;
}
int MIN(int a,int b){
    return a<b?a:b;
}
int main(){
    scanf("%d",&n);
    int x,y;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        op[make_pair(x+1,n-y)]++;
        ex[n-y].push_back(x+1);
    }
    for(int i=1;i<=n;i++){
        dp[i]=dp[i-1];
        for(int j=0;j<ex[i].size();j++){
            dp[i]=MAX(dp[ex[i][j]-1]+MIN(i-ex[i][j]+1,op[make_pair(ex[i][j],i)]),dp[i]);
        }
    //    printf("%d\n",dp[i]);
    }
    printf("%d",n-dp[n]);
//    while(1);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值