ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

两道 qoj

2022-06-04 19:32:10  阅读:230  来源: 互联网

标签:nxt int 线段 两道 add ls fi qoj


第一题

题意

数轴上有 \(n\) 条线段,你要在数轴上放点,使得每条线段至少包含一个点,最小化一条线段上最多放的点。

题解

神仙题。

首先有一个贪心想法,每次找到目前所有线段中最小的右端点,在这里放一个点,然后把包含了这个点的线段删掉,重复直到没有线段,设最后得到的答案为 \(K\)。

可以证明正确答案肯定为 \(K\) 或 \(K-1\)。

proof:首先考虑贪心得出的 \(K\) 个点中的两个相邻的点,因为右边的点是删去包含它之前的点的线段后的最小右端点,所以肯定存在一条线段右端点为它,且左端点没有超过左边的那个点,又因为每条线段都必须放一个点,所以在最优方案中,这两个相邻的点之间至少有一个点,而在 \(K\) 个点中有 \(K-1\) 对相邻的点,所以最优解大于等于 \(K-1\)。

上面这个结论帮助我们省去了二分答案的一个 log。现在我们只需要 check 一下 \(K-1\) 就好了。

考虑怎么 check 一个 \(X\)。

首先我们选的点只可能是线段端点,或者端点相邻的点。

DP 不太现实,贪心明显是错的,所以我们求出一个点如果不考虑完全在它左边的线段,它是否能存在于一组解中,设为 \(valid(i)\),想要求出 \(valid(i)\) 和一组解,还需要 \(nxt(i)\) 表示选了 \(i\),下一个最好选哪。

最好这个概念有一些模糊,其实它就是:

  1. \(valid(nxt(i))=true\)。
  2. \(i\) 到 \(nxt(i)\) 之间不能存在一条完整的线段,要不然这条线段就没有点了。
  3. \(nxt(i)\) 尽量大。

如果我们知道 \(i\) 后面的所有 \(j\) 的 \(nxt\) 和 \(valid\),是可以求出 \(nxt(i)\)。而且发现 \(valid(i)=true\) 其实就是:如果它不能是最后一个点的话,就必须要找得到 \(nxt(i)\),且对于 \(i,nxt(i),nxt(nxt(i))\ldots nxt^X(i)\),不存在一条线段能完整地保住这 \(X+1\) 个点。

所以就做完了,输出方案的话每次跳 \(nxt\) 就好了,实现的话 std::set 之类的乱搞就可以了。

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<algorithm>
#include<set>
#define fi first
#define se second
#define N 200005
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
int n,cnt,p[N],ans,lsh[2*N],m,mnr[2*N],vld[2*N],nxt[2*N][18],mnl[2*N],mx;
pair<int,int> a[N];
set<int> s;
multiset<int> ss;
int main(){
	for(int cas=read();cas--;){
		n=read();
		ss.clear();
		for(int i=1;i<=n;++i) a[i].fi=read(),a[i].se=read(),ss.insert(a[i].se);
		sort(a+1,a+n+1);
		cnt=0;
		int pos=0;
		while(pos<n){
			auto it=ss.begin();
			p[++cnt]=*it;
			while(pos+1<=n && a[pos+1].fi<=p[cnt]){
				++pos;
				auto fick=ss.lower_bound(a[pos].se);
				ss.erase(fick);
			}
		}
		ans=0;
		for(int i=1;i<=n;++i){
			int l=lower_bound(p+1,p+cnt+1,a[i].fi)-p;
			int r=upper_bound(p+1,p+cnt+1,a[i].se)-p-1;
			ans=max(ans,r-l+1);
		}
		ans--;
		m=0;
		for(int i=1;i<=n;++i) lsh[++m]=a[i].fi,lsh[++m]=a[i].se,lsh[++m]=a[i].se+1,lsh[++m]=a[i].fi-1;
		sort(lsh+1,lsh+m+1);
		m=unique(lsh+1,lsh+m+1)-lsh-1;
		for(int i=1;i<=m+1;++i) mnr[i]=mnl[i]=1e9+10;
		mx=0;
		for(int i=1;i<=n;++i){
			int l=lower_bound(lsh+1,lsh+m+1,a[i].fi)-lsh;
			int r=lower_bound(lsh+1,lsh+m+1,a[i].se)-lsh;
			mnr[l]=min(mnr[l],r);
			mnl[r]=min(mnl[r],l);
			mx=max(mx,l);
		}
		for(int i=m;i>=1;--i){
			mnr[i-1]=min(mnr[i-1],mnr[i]);
			mnl[i-1]=min(mnl[i-1],mnl[i]);
		}
		for(int i=1;i<=m;++i){
			vld[i]=0;
			for(int j=0;j<=17;++j) nxt[i][j]=0;
		}
		s.clear();
		for(int i=m;i>=1;--i){
			if(i>=mx){
				vld[i]=1;
				s.insert(i);
				continue;
			}
			auto it=s.upper_bound(mnr[i+1]);
			if(it==s.begin()){
				vld[i]=0;
				continue;
			}
			it--;
			nxt[i][0]=*it;
			for(int j=1;j<=17;++j) nxt[i][j]=nxt[nxt[i][j-1]][j-1];
			int now=i;
			for(int j=0;j<=17;++j) if(ans&(1<<j)) now=nxt[now][j];
			if(!now || mnl[now]>i) vld[i]=1;
			if(vld[i]) s.insert(i);
		}
		int ok=0;
		for(int i=1;i<=mnr[1];++i)
			if(vld[i]){ok=i;break;}
		if(ok && ans!=0){
			printf("%d ",ans);
			cnt=0;
			while(ok){
				p[++cnt]=lsh[ok];
				ok=nxt[ok][0];
			}
		}
		else printf("%d ",ans+1);
		printf("%d ",cnt);
		for(int i=1;i<=cnt;++i) printf("%d ",p[i]);
		puts("");
	}
	return 0;
}

第二题

题意

一个 \(n\times n\) 的二维平面,矩形加,矩形求 max,先修改再询问。

题解

因为 max 不具有可减性,直接扫的话不能处理,需要有一个统一的询问起点才可以扫,所以考虑对于第一维分治,第二维线段树,每次处理跨 mid 的询问。

但是一次修改能影响的分治区间很多,不能直接做,但是分治本身就是一个线段树的结构,如果我们如果到了这次修改完全包含的分治区间就打标记的话,就只用修改 \(\log n\) 个分治区间了,对于再下面的分治区间,我们在打标记的区间就修改好,不把修改去除就继续 solve 下去就可以了。

具体地在某个分治区间,我们需要从 mid 往一遍扫,线段是需要维护的是区间加,以及查询历史最大值。

但是到了实现的时候会有问题,我们每处理完一个分治区间不能把全部东西打个标记全清空了,因为我们有些修改操作是从上一层带下来的,难道我们要一步一步撤回吗?那样太阴间了。

有一个巧妙地做法,max 是不用清空的,因为扫进和扫出一个矩形一加一减刚好没了,我们只需清空历史最大值,我们可以新处理一个分治区间的时候给全局加一个 INF,这样历史最大值就被覆盖了,到时候询问再减掉就好。

INF 取 5e13 就好,时间复杂度 \(\mathcal{O}(m_1\log^2 n+m_2\log n)\)。

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<vector>
#define N 50005
#define int long long
#define ls k<<1
#define rs k<<1|1
#define fi first
#define se second
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
struct upd{
	int x1,y1,x2,y2,v;
};
struct qry{
	int x1,y1,x2,y2,id;
};
const int C=5e13;
struct segmentTree{
	int l,r,mx,add,hadd,his;
}d[4*N];
void build(int k,int l,int r){
	d[k].l=l,d[k].r=r;
	if(l==r) return;
	int mid=l+r>>1;
	build(ls,l,mid),build(rs,mid+1,r);
}
inline void pushdown(int k){
	if(!d[k].add && !d[k].hadd) return;
	d[ls].his=max(d[ls].his,d[ls].mx+d[k].hadd);
	d[rs].his=max(d[rs].his,d[rs].mx+d[k].hadd);
	d[ls].mx+=d[k].add,d[rs].mx+=d[k].add;
	d[ls].hadd=max(d[ls].hadd,d[ls].add+d[k].hadd);
	d[rs].hadd=max(d[rs].hadd,d[rs].add+d[k].hadd);
	d[ls].add+=d[k].add,d[rs].add+=d[k].add;
	d[k].add=d[k].hadd=0;
}
inline void update(int k,int x,int y,int v){
	if(x<=d[k].l && d[k].r<=y){
		d[k].his=max(d[k].his,d[k].mx+v);
		d[k].mx+=v;
		d[k].hadd=max(d[k].hadd,d[k].add+v);
		d[k].add+=v;
		return;
	}
	pushdown(k);
	int mid=d[k].l+d[k].r>>1;
	if(x<=mid) update(ls,x,y,v);
	if(mid+1<=y) update(rs,x,y,v);
	d[k].mx=max(d[ls].mx,d[rs].mx);
	d[k].his=max(d[ls].his,d[rs].his);
}
inline int query(int k,int x,int y){
	if(x<=d[k].l && d[k].r<=y) return d[k].his;
	pushdown(k);
	int mid=d[k].l+d[k].r>>1,res=0;
	if(x<=mid) res=query(ls,x,y);
	if(mid+1<=y) res=max(res,query(rs,x,y));
	return res;
}
int n,m1,m2,T,ans[N*10];
vector<upd> al[4*N],U[4*N];
vector<qry> Q[4*N];
void addUpdate(int k,int l,int r,upd v){
	if(v.x1<=l && r<=v.x2) return (void)(al[k].push_back(v));
	U[k].push_back(v);
	int mid=l+r>>1;
	if(v.x1<=mid) addUpdate(ls,l,mid,v);
	if(mid+1<=v.x2) addUpdate(rs,mid+1,r,v);
}
void addQuery(int k,int l,int r,qry v){
	int mid=l+r>>1;
	if(v.x1<=mid && mid+1<=v.x2) return (void)(Q[k].push_back(v));
	if(v.x2==mid || v.x1==mid+1) return (void)(Q[k].push_back(v));
	if(v.x1<=mid) addQuery(ls,l,mid,v);
	else addQuery(rs,mid+1,r,v);
}
vector<pair<pair<int,int>,int> > a[N],b[N],c[N];
void solve(int k,int l,int r){
	if(l==r) return;
	for(auto v:al[k]) update(1,v.y1,v.y2,v.v);
	for(int i=l-1;i<=r+1;++i) a[i].clear(),b[i].clear(),c[i].clear();
	int mid=l+r>>1;
	for(auto v:U[k]){
		int L=max(v.x1,l),R=min(mid,v.x2);
		if(L<=R){
			a[R].push_back({{v.y1,v.y2},v.v});
			c[L-1].push_back({{v.y1,v.y2},-v.v});
		}
	}
	for(auto v:Q[k]) if(v.x2>=mid) b[v.x1].push_back({{v.y1,v.y2},v.id});
	update(1,1,n,C),T+=C;
	for(int i=mid;i>=l-1;--i){
		for(auto v:c[i]) update(1,v.fi.fi,v.fi.se,v.se);
		for(auto v:a[i]) update(1,v.fi.fi,v.fi.se,v.se);
		for(auto v:b[i]) ans[v.se]=max(ans[v.se],query(1,v.fi.fi,v.fi.se)-T);
	}
	for(auto v:U[k]){
		int L=max(v.x1,mid+1),R=min(r,v.x2);
		if(L<=R){
			a[L].push_back({{v.y1,v.y2},v.v});
			c[R+1].push_back({{v.y1,v.y2},-v.v});
		}
	}
	for(auto v:Q[k]) if(v.x1<=mid+1) b[v.x2].push_back({{v.y1,v.y2},v.id});
	update(1,1,n,C),T+=C;
	for(int i=mid+1;i<=r+1;++i){
		for(auto v:c[i]) update(1,v.fi.fi,v.fi.se,v.se);
		for(auto v:a[i]) update(1,v.fi.fi,v.fi.se,v.se);
		for(auto v:b[i]) ans[v.se]=max(ans[v.se],query(1,v.fi.fi,v.fi.se)-T);
	}
	solve(ls,l,mid),solve(rs,mid+1,r);
	for(auto v:al[k]) update(1,v.y1,v.y2,-v.v);
}
signed main(){
	n=read(),m1=read(),m2=read();
	build(1,1,n);
	for(int i=1;i<=m1;++i){
		int x1=read(),y1=read(),x2=read(),y2=read(),v=read();
		addUpdate(1,1,n,{x1,y1,x2,y2,v});
	}
	for(int i=1;i<=m2;++i){
		int x1=read(),y1=read(),x2=read(),y2=read();
		addQuery(1,1,n,{x1,y1,x2,y2,i});
	}
	solve(1,1,n);
	for(int i=1;i<=m2;++i) printf("%lld\n",ans[i]);
	return 0;
}

标签:nxt,int,线段,两道,add,ls,fi,qoj
来源: https://www.cnblogs.com/xzzduang/p/16342544.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有