其他分享
首页 > 其他分享> > 两道 qoj

两道 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