两道 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\),下一个最好选哪。
最好这个概念有一些模糊,其实它就是:
- \(valid(nxt(i))=true\)。
- \(i\) 到 \(nxt(i)\) 之间不能存在一条完整的线段,要不然这条线段就没有点了。
- \(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