守卫-贪心,线段处理
P3634 守卫-贪心
P3634 守卫
这个线段处理值的学习
题意
给定区间及权值 \(0/1\),还有总共 \(1\) 的个数 \(k\) ,表示区间内是否有 \(1\) ,问一定为 \(1\) 的点有那些。
思路
很容易发现:
- \(0\) 的区间一定没有一。
- 去掉 \(0\) 之后长度刚好为 \(k\) 则一定有 \(1\) 。
- 值为 1 的区间长度恰好为 \(1\) 的时候,一定满足条件。
- 如果存在区间包含关系,保留小的区间。--小的区间外都可以为 \(0\) 。
考虑剩下的区间,容易发现如果在区间相交的地方存在 \(1\) ,可以减少剩余可能存在 \(1\) 的区间数量。因为如果权值为 \(1\) 的区间内有 \(1\) ,那剩余区间就是可能有 \(1\) ,不符合要求。可以用来判断无解。
考虑 \(1\) 最少的情况,因为此时总和最小,如果不选某个点导致总和超过 \(k\) ,则必须选这个点,因为无论如何构造都不会有方式让总和小于 \(k\)。
细节
- \(1\) 最少的情况用线段覆盖来求。
- 保留的线段权值都为 \(1\) ,且删去权值为 \(0\) 的线段。
- 判断是否无解要知道在这个点前面作最优解和这个点后面作最优解,所以对得到的最小情况要做一次前缀和和后缀和。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e5+10;
constexpr int INF = 0x3f3f3f3f3f3f3f3f;
typedef struct line
{
int l,r;
bool operator<(const line &o)const // l为第一关键字
{
return l!=o.l ? l<o.l : r<o.r;
}
}line;
int n,k,m;// 长,个数,区间数
int d[maxn];// 差分数组, 标记0的线段覆盖
int idx,cnt;// 线段数量,可能为1的点
line lin[maxn];// 线段-1
int pre[maxn],suf[maxn];// i左边可能为1的点的编号(cnt)
int sti[maxn];// 临时存储剩余的线段下标
int pre_cnt[maxn],suf_cnt[maxn];// 到某个线段最少要选取的点数(前后缀和)
int cov[maxn]; // cnt转坐标
signed main()
{
#ifndef ONLINE_JUDGE
freopen("cjdl.in","r",stdin);
freopen("cjdl.out","w",stdout);
#endif // ONLINE_JUDGE
scanf("%lld%lld%lld",&n,&k,&m);
for(int i=1,a,b,c ;i<=m;++i)
{
scanf("%lld%lld%lld",&a,&b,&c);
if(c==0)
{
++d[a];// 差分标记
--d[b+1];
}
else
{
++idx;
lin[idx]={a,b};
}
}
for(int i=1;i<=n;++i)
{
// 还原差分,找剩下1的点
d[i+1]+=d[i];
if(!d[i])
{
pre[i]=suf[i]=++cnt;
cov[cnt]=i;
}
}
if (k == cnt)// 如果刚好满足,必须要判!
{
for (int i = 1; i <= cnt; i++)
{
printf("%lld\n", cov[i]);
}
return 0;
}
for(int i=1;i<=n;++i)// 映射,似乎可以不用
{
if(!pre[i])
{
pre[i]=pre[i-1];
}
}
for(int i=n;i>=1;--i)
{
if(!suf[i])
{
suf[i]=suf[i+1];
}
}
for(int i=1;i<=idx;++i)
{
lin[i].l=suf[lin[i].l];// 跳过0的点
lin[i].r=pre[lin[i].r];
}
sort(lin+1,lin+1+idx);// 现在排序
for(int i=1;i<=idx;++i)
{
if(lin[i].l>lin[i].r)
{
continue;
}
while(sti[0] && lin[sti[sti[0]]].r>=lin[i].r)// 删除包含小线段的线段
{
--sti[0];
}
sti[++sti[0]]=i;
}
idx=sti[0];
for(int i=1;i<=idx;++i)
{
lin[i]=lin[sti[i]];// 挑完的线段
}
int rr=0;// 前缀最少要选取的点数
for(int i=1;i<=idx;++i)
{
if(lin[i].l>rr)
{
pre_cnt[i]=pre_cnt[i-1]+1;
rr=lin[i].r;
}
else
{
pre_cnt[i]=pre_cnt[i-1];
}
}
int ll=INF;// 后缀
for(int i=idx;i>=1;--i)
{
if(lin[i].r<ll)
{
suf_cnt[i]=suf_cnt[i+1]+1;
ll=lin[i].l;
}
else
{
suf_cnt[i]=suf_cnt[i+1];
}
}
bool flag=0;
for(int i=1;i<=idx;++i)
{
if(lin[i].l==lin[i].r)// 一个点必须要
{
flag=1;
printf("%lld\n",cov[lin[i].l]);
continue;
}
if(pre_cnt[i] != pre_cnt[i-1]+1) continue;// 不是有影响的线段右端点
int pos=idx+1;
int l=i+1,r=idx;// 二分找下一个线段(左端点刚好大) 可以用双指针更快
while(l<=r)
{
int mid=l+((r-l)>>1);
if(lin[mid].l>lin[i].r-1)
{
pos=mid;
r=mid-1;
}
else
{
l=mid+1;
}
}
if(pre_cnt[i]+suf_cnt[pos]>k)// 不选这个点无解
{
flag=1;
printf("%lld\n",cov[lin[i].r]);
}
}
if(!flag)// 没有选一个点
{
printf("-1\n");
}
return 0;
}

浙公网安备 33010602011771号