做法:按照row编号从小到大进行dp,计算出当前行覆盖区域的最大连接数,以及连接的上一个row编号。由于是区间覆盖问题,可以用线段树来加速这一过程,注意用pair<int,int> mx[N<<3]来记录{连接数,上一row编号}。
#include <bits/stdc++.h>
#define debug cout<<"!!!"<<endl;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
#define pii pair<int,int>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N = 3e5+5;
int n,m;
vector<pii> row[N];
int C[N],L[N],R[N],pre[N]; //row_id,left,right,pre前驱
vector<int> b;
bool vis[N];
pii mx[N<<3],lz[N<<3];
void pushdn(int p){
lz[ls]=max(lz[ls],lz[p]);
lz[rs]=max(lz[rs],lz[p]);
mx[ls]=max(mx[ls],lz[p]);
mx[rs]=max(mx[rs],lz[p]);
lz[p] = {0,0};
}
void upd(int p,int l,int r,int x,int y,int val,int id){
if(x<=l && r<=y){
lz[p] = max(lz[p],{val,id});
mx[p] = max(mx[p],{val,id});
return;
}
pushdn(p);
int mid = l+r>>1;
if(x<=mid) upd(ls,l,mid,x,y,val,id);
if(mid<y) upd(rs,mid+1,r,x,y,val,id);
mx[p] = max(mx[ls],mx[rs]);
}
pii ask(int p,int l,int r,int x,int y){
if(x<=l && r<=y) return mx[p];
pushdn(p);
int mid = l+r>>1;
pii res = {0,0};
if(x<=mid) res = max(res,ask(ls,l,mid,x,y));
if(y>mid) res = max(res,ask(rs,mid+1,r,x,y));
return res;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;++i){ //首先,输入所有数据,把数字放在b里面,用于后续离散化
cin>>C[i]>>L[i]>>R[i];
b.push_back(L[i]); b.push_back(R[i]);
}
sort(b.begin(),b.end()); //b离散化前要排序
b.erase(unique(b.begin(),b.end()),b.end()); //b数组进行去重(这一步没有也可,但是有的话能减少点数,提高效率)
int len = b.size(); //b数组大小,和线段树大小有关
for(int i=1;i<=m;++i){ //遍历所有线段
L[i]=lower_bound(b.begin(),b.end(),L[i])-b.begin()+1; //生成离散值
R[i]=lower_bound(b.begin(),b.end(),R[i])-b.begin()+1;
row[C[i]].push_back({L[i],R[i]}); //这一行放入这段信息
}
for(int i=1;i<=n;++i){ //遍历每一行
pii dp={0,0}; //当前行的dp值
for(auto P:row[i]) dp=max(dp,ask(1,1,len,P.first,P.second)); //求出之前的最大覆盖次数,以及对应的row_id
pre[i] = dp.second; //记录pre
for(auto st:row[i]) upd(1,1,len,st.first,st.second,dp.first+1,i); //全部都要更新一遍
}
cout<<n-mx[1].first<<'\n'; //输出要删除的行数的min值
int now=mx[1].second; //最后一个要选的编号
while(now){ //一路往前寻找要遍历的编号,这些编号的vis为1,表示不用删除,其他都要删除
vis[now]=1; //标记过的,不用删,也就是不用输出
now=pre[now]; //往前遍历
}
for(int i=1;i<=n;i++) if(!vis[i]) cout<<i<<' '; //输出答案
return 0;
}