[HAOI2011] Problem a: Solution

Comprehension:

有个不严格单调上升的n元序列,给定n个区间( l i l_i li, r i r_i ri),表示这区间的位置是。但是数字是有限并且非严格单调的,因此某些区间和前面的区间矛盾,求这些矛盾区间的最小个数。

Analysis:

很容易发现这道题给定的相当于是两个开区间( 0 0 0, l i l_i li)和( r i r_i ri, n n n)。由于前后都是不等关系,这个东西很让人头疼。我们给他改一下,变成一个闭区间[ l i l_i li, r i r_i ri].相当于是一条线段,这条线段中所有的数都应该是相等的。

再来考虑,如果我们直接求互斥的个数,似乎比较难以维护,因为这个取舍不太好做。想想我们学过一个模型,就是线段覆盖问题,这个东西就可以用动态规划去搞一搞。

所以,我们把这个问题转化成为:n个等值区间,求最多的不矛盾的区间个数。

Consideration:

定义变量如下:

int dp[maxn];//dp[i]表示从第1名到第i名,排在这些名次的人最多有多少,也就是这范围内最多有多少个人说了真话 
int n;
map<pair<int,int>,int> qwq;//用来统计区间出现次数 
vector<int> clm[maxn];//用来记录每个区间。 

对于整个过程,我们大致做一个宏观的概述:

  1. 将每个约束条件转换为闭区间,闭区间表示并列名次
  2. 对于每个点 i i i,有多少个点落在这个点上,用动态数组clm(即claim)来维护;对于每个区间[ l i l_i li, r i r_i ri],统计出现的次数,用一个映射qwq来维护
  3. 升序扫描每个点(也就是排名),首先令 d p [ i ] dp[i] dp[i]继承 d p [ i − 1 ] dp[i-1] dp[i1]的答案,因为我们用dp数组统计的是人数,从定义上来看就应该是不下降的,因此我们继承。
  4. (1)对 d p [ i ] dp[i] dp[i]转移。转移的对象是以当前节点i结尾的所有区间。但是如果某区间出现次数大于区间长度那肯定有问题,取min。
    (2)令 s = c l m [ i ] [ j ] s=clm[i][j] s=clm[i][j]表示区间左端,这区间是 [ s , i ] [s,i] [s,i].从 s − 1 s-1 s1号点转移, [ s , i ] [s,i] [s,i]的出现次数(qwq统计)全部算作真话,累加.(3)是否需要考虑其他区间呢?不需要的。因为那些更小或者更大的区间,如果也以 s s s为起点或者以 i i i为终点,一定会被另行转移。只考虑当前的就好了。

Explanation:

所以我们可以写出状态转移方程如下:

int limit=clm[i].size();
if(limit==0) continue;
for(register int j=0;j<limit;++j)
   	dp[i]=max(dp[clm[i][j]-1]+min(i-clm[i][j]+1,qwq[make_pair(clm[i][j],i)]),dp[i]);

对这个方程进行解释:
m i n ( i − c l m [ i ] [ j ] + 1 , q w q [ m a k e min(i-clm[i][j]+1,qwq[make min(iclm[i][j]+1,qwq[make_ p a i r ( c l m [ i ] [ j ] , i ) ] ) pair(clm[i][j],i)]) pair(clm[i][j],i)])前者是当前区间的长度,后者是这个区间被claim了多少次。为了防止上面提到的小问题,取最小值,多出来的一定没说真话。

m a x ( d p [ c l m [ i ] [ j ] − 1 ] + max(dp[clm[i][j]-1]+ max(dp[clm[i][j]1]+上式 , d p [ i ] ) ,dp[i]) ,dp[i]),前者表示当前区间前段点再前面的一个节点,这是用来作转移的。

最后得出的 d p [ n ] dp[n] dp[n]表示从rank1到rankn中最多的说真话的人数。那么我们所需要的答案就是 n − d p [ n ] n-dp[n] ndp[n]

Completed Code:

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define maxn 200200
using namespace std;
inline int read()
{
    int n=0,k=1;
    char c=getchar();
    while(c>'9'||c<'0') {if(c=='-') k=-1; c=getchar();}
    while(c<='9'&&c>='0') n=(n<<1)+(n<<3)+(c^48),c=getchar();
    return n*k;
}
int dp[maxn];//dp[i]表示从第1名到第i名,排在这些名次的人最多有多少,也就是这范围内最多有多少个人说了真话 
int n,m;
map<pair<int,int>,int> qwq;//用来统计区间出现次数 
vector<int> clm[maxn];//用来记录每个区间。 
int main()
{
    n=read();
    for(register int i=1;i<=n;++i)
    {
        int l=read()+1,r=n-read();//将(0,l)和(r,n)开区间 转化为维护并列名次的 [l,r]闭区间,即可用线段覆盖模型 
        if(l>r) continue;
        ++qwq[make_pair(l,r)];//统计次数 
        if(qwq[make_pair(l,r)]==1) clm[r].push_back(l);
    }
    memset(dp,0,sizeof(dp));
    for(register int i=1;i<=n;++i)
    {
        dp[i]=dp[i-1];//继承答案,显然这答案具有单调性 
        int limit=clm[i].size();
        if(limit==0) continue;
        for(register int j=0;j<limit;++j)
            dp[i]=max(dp[clm[i][j]-1]+min(i-clm[i][j]+1,qwq[make_pair(clm[i][j],i)]),dp[i]);
    }
    printf("%d\n",n-dp[n]);//要求最少说假话的人,那么总人数减去最多说真话的人即可 
    return 0;
}

Moreover:

关于时间复杂度:dp中一重外循环 O ( n ) O(n) O(n),内部虽然有j但是每个区间只被访问一次,也是 O ( n ) O(n) O(n),而内部调用 q w q qwq qwq的时间复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n),因此总的时间复杂度是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。实测最大数据75ms(我常数巨大求轻d)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值