队内训练3 Codeforces1557D 线段树存pair优化dp

队内训练3 Codeforces1557D 线段树存pair优化dp

题目链接link.

题意:给你一个n行的01矩阵,m个连续的11…1序列。要求你去删一些行,让最后得到的新的矩阵,每两行之间都有相同的位置有1。问你最少删除多少行以及删除哪些行。多种方案则输出任意一种。

首先,这个题必然不是贪心,直观的感受就是dp。可以很容易想到 O ( N 2 ) O(N^2) O(N2) 的dp做法。 d p [ i ] dp[i] dp[i] 代表前 i i i 行最多留多少行可以保证题目要求。转移方程为 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i]=max(dp[i],dp[j]+1) dp[i]=max(dp[i],dp[j]+1)。其中 j j j 1 1 1 i − 1 i-1 i1中与 i i i 相邻符合条件的。但是n和m范围都是1e5, O ( n 2 ) O(n^2) O(n2)肯定 tle的头都飞了。考虑用数据结构来维护一下这个转移。

可以考虑以权值建线段树。tree[i]表示当前第i个位置上有1的能保留的行数的最大值。那么我们只需要找每一行有1的tree[i]的最大值更新就好。

但是还有一个比较棘手的问题就是,需要记录dp的路径。也就是说要找到我们取得的最大值的行号。解决方案就是用pair去存行号。操作和普通的线段树是一样的,更新也是更新max,但是行号跟着pair也被记录下来了,这样就可以解决输出删除方案的问题。

每天一个线段树魔改小技巧

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
int n,m;
map<int,int> ok;
int lsh[600010];
struct oxy
{
	vector<pair<int,int>> haha;
	
}a[300010];

int dp[300010];

pair<int,int> max(pair<int,int>x,pair<int,int>y)
{
	if(x.first>y.first) return x;
	return y;
}

struct QwQ
{
	int l,r;
	pair<int,int> value;
	pair<int,int> lazy_tag={0,0};
}tree[4*600010];

void build(int x,int l,int r)
{
	tree[x].l=l;
	tree[x].r=r;
	tree[x].lazy_tag={0,0};
	if(l==r)
	{
		tree[x].value={0,0};
		return ;
	}
	int mid=(l+r)/2;
	build(x*2,l,mid);
	build(x*2+1,mid+1,r);
	tree[x].value=max(tree[x*2].value,tree[x*2+1].value);
}
void update(int x,pair<int,int> w)
{
	tree[x].value= max(tree[x].value,w);
	tree[x].lazy_tag=w;
	return ;
}
void push_down(int x)
{
	update(x*2,tree[x].lazy_tag);
	update(x*2+1,tree[x].lazy_tag);
	tree[x].lazy_tag={0,0};
}
void push_up(int x)
{
	tree[x].value=max(tree[x*2].value ,tree[x*2+1].value);
}
void change(int x,int l,int r,pair<int,int> w)
{	
	if(tree[x].l==l&&tree[x].r==r)
	{
		update(x,w);
		return ;
	}
	if(tree[x].lazy_tag.second!=0) push_down(x);
	
	int mid=(tree[x].l+tree[x].r)/2;
	if(r<=mid) change(x*2,l,r,w);
	else if(l>=mid+1) change(x*2+1,l,r,w);
	else
	{
		change(x*2,l,mid,w);
		change(x*2+1,mid+1,r,w);
	}
	push_up(x);
}
pair<int,int> query(int x,int l,int r)
{
	if(tree[x].l==l&&tree[x].r==r)
	{
		return tree[x].value;	
	}
	
	if(tree[x].lazy_tag.second!=0) push_down(x);
	int mid=(tree[x].l+tree[x].r)/2;
	if(r<=mid)
	{
		return query(x*2,l,r);
	}
	else if(l>=mid+1)
	{
		return query(x*2+1,l,r);
	}
	else
	{
		return max(query(x*2,l,mid),query(x*2+1,mid+1,r));
	}
} 

int pre[300010];
int miao[300010];
int main( )
{
	scanf("%d%d",&n,&m);
	int sum=0;
	int maxx=0;
	for(int i=1;i<=m;i++)
	{
		int k,l,r;
		scanf("%d%d%d",&k,&l,&r);
		if(ok[l]==0)
		{
			lsh[++sum]=l;
			ok[l]++;
		}
		if(ok[r]==0)
		{
			lsh[++sum]=r;
			ok[r]++;
		}
		a[k].haha.push_back({l,r});
		
		if(k>=maxx) maxx=k;
	}
	
	sort(lsh+1,lsh+sum+1);
	for(int i=1;i<=sum;i++)
	ok[lsh[i]]=i;
	int ans=0;
	int anss=0;
	build(1,1,sum);
	int maxxx=0;
	
	for(int i=1;i<=maxx;i++)
	{
		maxxx=0;
		for(auto j:a[i].haha)
		{
			int l=j.first;
			int r=j.second;
			l=ok[l];
			r=ok[r];
			int xixi=query(1,l,r).first+1;
			int orz=query(1,l,r).second;
			//cout<<"xixi="<<xixi<<" orz="<<orz<<endl;
			if(xixi>=maxxx) maxxx=xixi,pre[i]=orz;
		}
		
		for(auto j:a[i].haha)
		{
			int l=j.first;
			int r=j.second;
			l=ok[l];
			r=ok[r];
			change(1,l,r,{maxxx,i});
		}
		
		//cout<<"pre["<<i<<"]="<<pre[i]<<endl;
		
		if(maxxx>ans) ans=maxxx,anss=i;
	}
	printf("%d\n",n-ans);
	if(n-ans==0) return 0;
	
	miao[anss]++;
	while(1)
	{
		anss=pre[anss];
		miao[anss]++;
		if(anss<=0) break;
		//cout<<"anss="<<anss<<endl;
	}
	
	int okkk=0;
	for(int i=1;i<=n;i++)
	{
		if(miao[i]==0) 
		{
			if(okkk!=0) printf(" ");
			okkk++,printf("%d",i); 
		}
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值