Problem
有
n
n
个灌木丛,其中有 个后面有士兵
m
m
个区间,每个区间至少有 名士兵 (
op=1
o
p
=
1
),或者一个士兵都没有
(op=0)
(
o
p
=
0
)
问哪个灌木丛后面一定有士兵
Solution
首先,我们可以把是
0
0
的区间删去,然后对序列进行重标号
做法:差分/线段树
重标号后, 发生变换
此时这
n
n
个点都是可以放士兵的
那么我们可以特判掉 的情况
然后我们发现如果又两个区间
(l1,r1),(l2,r2)
(
l
1
,
r
1
)
,
(
l
2
,
r
2
)
,且
l1<=l2<=r2<=r1
l
1
<=
l
2
<=
r
2
<=
r
1
那
(l1,r1)
(
l
1
,
r
1
)
中除去
(l2,r2)
(
l
2
,
r
2
)
部分,都是可有可无的。那我们就删去第一个区间,只保留第二个
做法:把所有区间按
l
l
从大到小排序,那么此时对于每个一点 ,那么他之前的点
j
j
一定会有 ,那么如果存在
rj<=ri
r
j
<=
r
i
,则就有
li<=lj<=rj<=ri
l
i
<=
l
j
<=
r
j
<=
r
i
,那么
(li,ri)
(
l
i
,
r
i
)
就可以删去了。
(脑残的用了set….)
此时,所有的
l、r
l
、
r
分别都是递增的了(这个一会会用到)
然后我们预处理出
f[i]、g[i]
f
[
i
]
、
g
[
i
]
分别表示从前到后、从后到前最少放多少兵能满足前/后
i
i
个条件
做法(对于从前到后,从后到前反过来就行):按照左端点排序,记录前一个放士兵放在哪里,如果这个士兵正好在这个区间内,那么说明这个区间不再需要其他的点,而如果不在,那我们就将这个区间的右端点放上士兵(这个一定是最优的,对于后面的来说)。此时我们还要记录这个区间的右端点需不需要放上士兵(同后面需要用)。
题目中询问的是有哪个点必须放上士兵,根据之前求出的 ,这些士兵出自区间右端点,然而不一定是必须的。那我们考虑对于每一个区间,如果右端点前一个点放上士兵同样能满足
k
k
这个条件,那么说明右端点不是必须的放上士兵;反之,必须此右端点必须放上士兵。那我们就是去找前一个点所在的区间 ,那么此时我们需要的士兵数就是
f[l−1]+g[r+1]+1
f
[
l
−
1
]
+
g
[
r
+
1
]
+
1
,若此数
<=k
<=
k
<script type="math/tex" id="MathJax-Element-36"><=k</script> 则说明在右端点前一个点放上士兵也可以满足条件,此右端点不符合题目要求;反之输出即可
对于寻找
x
x
所在的区间 ,我用的是二分。但是吧…其实…因为
x=
x
=
右端点
−1
−
1
,那么右端点是递增的,右端点减一也是递增的, 所有直接推着走就行了
良心的几点注意(wa的猝不及防):
- 要记得还要输出 −1 − 1
- 对于最后的部分,要考虑此区间右端点是否被放置了士兵,若没放置,则不存字寻找包含一说
- 要注意有的区间可能没有右端点减一这个点,即区间只有一个数,那么就直接输出好了
Code
#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
#define N 100010
set<int>S;
int n,m,k,cnt=0,sz=0,b[N],mx[N],mn[N],f[N],g[N],val[N],isr[N];
struct node{int x,y,op;}a[N],c[N];
inline bool cmp1(node x,node y){return x.x>y.x || (x.x==y.x && x.y<y.y);}
inline bool cmp2(node x,node y){return x.x<y.x;}
inline bool cmp3(node x,node y){return x.x>y.x;}
int main(){
freopen("in.txt","r",stdin);
freopen("a.in","w",stdout);
scanf("%d%d%d",&n,&k,&m);int m1=0;
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
if(!z) b[x]++,b[y+1]--;
else a[++m1].x=x,a[m1].y=y;
}m=m1;
//重标号
for(int i=1;i<=n;i++) b[i]+=b[i-1];
int now=0;
for(int i=1;i<=n;i++){
if(b[i]<=0) val[++now]=i;
}
now=0;
for(int i=1;i<=n;i++){
if(!b[i]) now++;
mn[i]=now;
}
now++;
for(int i=n;i>=1;i--){
if(!b[i]) now--;
mx[i]=now;
}
for(int i=1;i<=m;i++){
a[i].x=mx[a[i].x];
a[i].y=mn[a[i].y];
}
n=mn[n];
//特判
if(n==k){
for(int i=1;i<=n;i++) printf("%d\n",val[i]);
return 0;
}
//删去包含其他线段的线段
sort(a+1,a+m+1,cmp1);
for(int i=1;i<=m;i++){
if(i!=1 && a[i].y>= *S.begin()) a[i].op=1;
else S.insert(a[i].y);
}
m1=0;
for(int i=1;i<=m;i++) if(!a[i].op){
c[++m1]=a[i];
}
m=m1;
for(int i=1;i<=m;i++) a[i]=c[i];
//求 f、g 数组
sort(a+1,a+m+1,cmp2);
int pre=0;
for(int i=1;i<=m;i++){
f[i]=f[i-1];
if(a[i].x>pre) f[i]++,isr[i]=1,pre=a[i].y;
}
pre=0x3f3f3f3f;
for(int i=m;i>=1;i--){
g[i]=g[i+1];
if(a[i].y<pre) g[i]++,pre=a[i].x;
}
//二分查找区间,判断结果
for(int i=1;i<=m;i++){
if(!isr[i]) continue;
int l=1,r=i-1,ans1=i;
if(a[i].x==a[i].y){printf("%d\n",val[a[i].x]),sz++;continue;}
while(l<=r){
int mid=l+r>>1;
if(a[mid].x<=a[i].y-1 && a[i].y-1<=a[mid].y) ans1=mid,r=mid-1;
else l=mid+1;
}
l=i+1;r=m;int ans2=i;
while(l<=r){
int mid=l+r>>1;
if(a[mid].x<=a[i].y-1 && a[i].y-1<=a[mid].y) ans2=mid,l=mid+1;
else r=mid-1;
}
if(f[ans1-1]+g[ans2+1]+1>k) printf("%d\n",val[a[i].y]),sz++;
}
if(!sz) puts("-1");
return 0;
}