题面
https://www.luogu.com.cn/problem/P3634
给m个限制,可以是一段区间中必须有或者必须无忍者
最多有k个忍者,问有多少个位点一定有忍者
分析
首先用差分标记一下0忍者的区间,去掉
然后再删去包含了其他区间的区间,没有意义
将剩余区间按左端点排序,方便处理
考虑计算处理到第i个区间所需的最少忍者数,如果该区间内没有已经被选择的点,贪心选择右端点使总点数最少
倒着处理一遍,类似,不过是贪心选择左端点
枚举每个右端点考虑不选择它而选择它的左侧端点,并二分找到不与它相交的最近的左右两个区间
若从左侧开始处理到左侧区间的最少忍者数+从右侧开始处理到右侧区间的最少忍者数+1(选择当前区间)的所需忍者数大于k,那么说明不选这个右端点是不合法的,所以这个点必选
代码
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int N=1e5+10; struct Guard { int a,b,c; }g[N]; int n,k,m,zm,gcnt; int zer[N],ref[N],rcnt,nearl[N],nearr[N],f[N],h[N]; bool ans; bool CMP(Guard a,Guard b) {return a.a<b.a;} int main() { scanf("%d%d%d",&n,&k,&m); for (int i=1;i<=m;i++) { scanf("%d%d%d",&g[i].a,&g[i].b,&g[i].c); if (!g[i].c) zer[g[i].a]++,zer[g[i].b+1]--; } for (int i=1;i<=n;i++) { zer[i]+=zer[i-1]; if (!zer[i]) ref[nearl[i]=nearr[i]=++rcnt]=i; else nearl[i]=n+1; } if (rcnt==k) {for (int i=1;i<=rcnt;i++) printf("%d ",ref[i]);return 0;} for (int i=2;i<=n;i++) nearr[i]=max(nearr[i],nearr[i-1]),nearl[n-i+1]=min(nearl[n-i+1],nearl[n-i+2]); for (int i=1;i<=m;i++) if (g[i].c&&nearl[g[i].a]<=nearr[g[i].b]) g[++zm].a=nearl[g[i].a],g[zm].b=nearr[g[i].b]; sort(g+1,g+zm+1,CMP); for (int i=1;i<=zm;i++) { while (gcnt&&g[gcnt].a<=g[i].a&&g[i].b<=g[gcnt].b) gcnt--; g[++gcnt]=g[i]; } for (int i=1,r=0;i<=gcnt;i++) if (g[i].a>r) f[i]=f[i-1]+1,r=g[i].b; else f[i]=f[i-1]; for (int i=gcnt,l=n+1;i;i--) if (g[i].b<l) h[i]=h[i+1]+1,l=g[i].a; else h[i]=h[i+1]; for (int i=1,l,r,mid,ansl,ansr;i<=gcnt;i++) if (f[i]!=f[i-1]) if (g[i].a==g[i].b) printf("%d ",ref[g[i].b]),ans=1; else { l=1;r=i-1;ansl=0; while (l<=r) { mid=l+r>>1; if (g[mid].b<g[i].b-1) l=mid+1,ansl=mid; else r=mid-1; } l=i+1;r=gcnt;ansr=n+1; while (l<=r) { mid=l+r>>1; if (g[mid].a>g[i].b-1) r=mid-1,ansr=mid; else l=mid+1; } if (f[ansl]+1+h[ansr]>k) printf("%d ",ref[g[i].b]),ans=1; } if (!ans) printf("-1 "); }