给了n个01串,每个串都是1e9的长度。
一个01串集合是优美的当且仅当相邻的任意两个串中,至少存在一列,满足在这两个串里面都是1。
问最少删去多少个串,才能使得剩下的串构成一个优美集合。
题目等价于可以从中挑出多少个串,使得任意相邻的两个串都满足条件。
我们先思考一个暴力解法:
dp[i]表示前i个串中,最多可以挑出多少个串构成以串i结尾的优美集合。
求解dp[i]:
先枚举串i的每一个1的位置j,然后枚举k,(1<=k<=i-1),且串k的j位置上也是1
,取dp[i]=max(dp[i],dp[k]+1)
,
当然,这里实质就是在满足条件的dp[k]中取max。
时间复杂度n * 1e9 * n
怎么去优化呢。
我们可以对01串的[1,1e9]区间建权值线段树,维护当前位置的最大答案,如果以串i结尾的答案求出来等于A,那么在线段树中把串i每个1的位置都更新,与A取max 。
相当于用线段树维护max,记录了dp[i],且保存了取最大值的结尾串下标。
由于一共只有m个区间是1,所以复杂度够,很巧妙。
具体流程:
线段树维护j位置目前的最大答案,由于要输出方案,我们再记录一下这个最大答案是以哪个串作为结尾贡献的。
我们从上往下枚举串i,
对于串i,枚举他的每个1的区间,然后区间查询最大值,取max,
最大值加1,就是串i结尾的最大答案。
重新枚举他的每个1区间,用串i的答案去区间更新。
如果不离散化的话时间复杂度是O(M * logINF )
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 6e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int b[maxn];
int n,n0,m;
vector<pii> a[maxn];
struct node
{
#define lc rt<<1
#define rc rt<<1|1
int l,r;
pii f;//max idx
}tree[maxn<<2];
pii tag[maxn<<2];
inline void pushup(int rt)
{
tree[rt].f = max(tree[lc].f , tree[rc].f);
}
void build(int rt,int l,int r)
{
tag[rt]={0,0};
tree[rt].l=l,tree[rt].r=r;
if(l==r)
{
tree[rt].f={0,0};
}
else
{
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
}
inline void change(int rt,pii v)
{
tree[rt].f=max(tree[rt].f,v);
tag[rt]=max(tag[rt],v);
}
inline void pushdown(int rt)
{
if(tag[rt].first)
{
change(lc,tag[rt]);
change(rc,tag[rt]);
tag[rt]={0,0};
}
}
inline void upd(int rt,int vl,int vr,pii v)
{
int l=tree[rt].l,r=tree[rt].r;
if(r<vl || l>vr) return ;
if(vl<=l && r<=vr)
{
change(rt,v);
return ;
}
pushdown(rt);
upd(lc,vl,vr,v);
upd(rc,vl,vr,v);
pushup(rt);
}
inline pii qry(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(vl<=l && r<=vr)
{
return tree[rt].f;
}
pushdown(rt);
int mid=l+r>>1;
if(vr<=mid) return qry(lc,vl,vr);
else if(vl>mid) return qry(rc,vl,vr);
return max(qry(lc,vl,vr),qry(rc,vl,vr));
}
int pre[maxn];
bool vis[maxn];
int main()
{
scanf("%d %d",&n,&m);
for(int i=1,t,x,y;i<=m;i++)
{
scanf("%d %d %d",&t,&x,&y);
a[t].push_back({x,y});
b[++n0]=x;
b[++n0]=y;
}
//离散化
sort(b+1,b+n0+1);
n0=unique(b+1,b+1+n0)-b-1;
build(1,1,n0);
for(int i=1;i<=n;i++)
{
for(pii &t:a[i])
{
t.first=lower_bound(b+1,b+n0+1,t.first)-b;
t.second=lower_bound(b+1,b+n0+1,t.second)-b;
}
}
for(int i=1;i<=n;i++)
{
pii mx={0,0};
if(a[i].empty()) continue;
for(pii &t:a[i])
{
mx=max(mx,qry(1,t.first,t.second));//每段1的对应的最大答案
}
if(!mx.first) mx.second=i;//没有 那么目前这个位置的最大答案就是1,在i串产生
mx.first++;
//mx.first:以串i结尾的最大答案
pre[i]=mx.second;//由mx.second这个串结尾的答案转移而来
for(pii &t:a[i])
{
upd(1,t.first,t.second,make_pair(mx.first,i));
}
}
pii mx=qry(1,1,n0);
printf("%d\n",n-mx.first);
int idx=mx.second;
do
{
vis[idx]=1;
int t=idx;
idx=pre[idx];
if(t==idx) break;
} while(1);
for(int i=1;i<=n;i++)
if(!vis[i]) printf("%d ",i);
return 0;
}