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];//用来记录每个区间。
对于整个过程,我们大致做一个宏观的概述:
- 将每个约束条件转换为闭区间,闭区间表示并列名次
- 对于每个点 i i i,有多少个点落在这个点上,用动态数组clm(即claim)来维护;对于每个区间[ l i l_i li, r i r_i ri],统计出现的次数,用一个映射qwq来维护
- 升序扫描每个点(也就是排名),首先令 d p [ i ] dp[i] dp[i]继承 d p [ i − 1 ] dp[i-1] dp[i−1]的答案,因为我们用dp数组统计的是人数,从定义上来看就应该是不下降的,因此我们继承。
- (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 s−1号点转移, [ 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(i−clm[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] n−dp[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)