CF #737 (Div. 2) D. Ezzat and Grid(线段树加速dp)

做法:按照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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值