题解 P6109 【[Ynoi2009]rprmq】
一丶前言
这道题可能是本蒟蒻做过的最巧的数据结构题,没有之一qaq。
二丶思路
我们可以先考虑这样一种做法。首先对于
我们考虑从小到大枚举
我们询问的是什么呢?可以发现,其实我们询问的其实是历史最大值,而修改操作就是一个区间加,所以我们只要维护一个可以支持区间历史最大值和区间加的线段树即可(CPU监控简化版)。
由于这个东西比较简单,就不细讲怎么维护了
注意一个小细节,若一个时间点有多次修改,要先加负数再加正数,不然历史最大值可能只加入一个时间点的部分操作,这样是不合法的。
这样时间复杂度是(好像时间复杂度更高了),但可喜可贺的是我们把询问从两个
考虑在这个算法的基础上继续进行优化。
可以发现对于一个询问,我们要将
考虑线段树分治,假设当前线段树节点代表的区间为
对于所有
我们假设
考虑先处理右边的贡献,所以先把
然后考虑处理左边的贡献,按
(前面那堆东西可能有点冗杂,但如果理解了前面的话再看看代码应该就没问题了)
这样时间复杂度就是
三丶代码
因为lxl貌似并不希望有人抄代码所以去了头文件。
//W4P3R
#define N 500010
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline ll read()
{
char ch=getchar();
ll s=0,w=1;
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
ll n,m,q,ans[N];
struct TREE{ll maxn,hmax,tag1,tag2,res;};
struct ask{ll id,l,r,x,y;}a[N];
struct node{ll l,r,val;};vector<node>v[N];
vector<ask>que[N<<2];
struct Segement_Tree
{
TREE t[N<<2];
inline void addtag(ll x,ll v1,ll v2)
{
t[x].hmax=max(t[x].maxn+v2,t[x].hmax);t[x].maxn+=v1;
t[x].tag2=max(t[x].tag1+v2,t[x].tag2);t[x].tag1+=v1;
}//维护历史最大值
inline void reset(ll x)
{
addtag(ls,t[x].tag1,t[x].tag2);addtag(rs,t[x].tag1,t[x].tag2);
t[x].tag1=t[x].tag2=0;
t[x].hmax=t[x].maxn;t[x].res=1;
}//把历史最大值重置为当前最大值
inline void pushdown(ll x)
{
if(t[x].res){reset(ls);reset(rs);t[x].res=0;}
addtag(ls,t[x].tag1,t[x].tag2),addtag(rs,t[x].tag1,t[x].tag2);
t[x].tag1=t[x].tag2=0;
}
inline void pushup(ll x)
{
t[x].maxn=max(t[ls].maxn,t[rs].maxn);
t[x].hmax=max(t[ls].hmax,t[rs].hmax);
}
void update(ll l,ll r,ll x,ll left,ll right,ll val)
{
if(l==left&&right==r){addtag(x,val,val);return ;}
pushdown(x);
if(right<=mid)update(l,mid,ls,left,right,val);
else if(left>mid)update(mid+1,r,rs,left,right,val);
else update(l,mid,ls,left,mid,val),update(mid+1,r,rs,mid+1,right,val);
pushup(x);
}
ll query(ll l,ll r,ll x,ll left,ll right)
{
if(l==left&&right==r)return t[x].hmax;
pushdown(x);
if(right<=mid)return query(l,mid,ls,left,right);
else if(left>mid)return query(mid+1,r,rs,left,right);
else return max(query(l,mid,ls,left,mid),query(mid+1,r,rs,mid+1,right));
}
}T;//维护历史最大值的线段树
void add(ll x,ll l,ll r,ask p)
{
if(p.l<=mid+1&&mid<=p.r){que[x].pb(p);return ;}//把询问加入当前点
if(p.r<=mid)add(ls,l,mid,p);
else add(rs,mid+1,r,p);
}
inline ll cmp(node a,node b){return a.val<b.val;}
inline ll cmp1(ask a,ask b){return a.r<b.r;}
inline ll cmp2(ask a,ask b){return a.l>b.l;}
inline void Up(ll x,ll opt)
{
if(opt==1){for(auto p:v[x]){T.update(1,n,1,p.l,p.r,p.val);}}
else {REP(i,v[x].size()-1,0)T.update(1,n,1,v[x][i].l,v[x][i].r,-v[x][i].val);}
}//1是加入操作,-1是回退操作
void Solve(ll x,ll l,ll r)
{
FOR(i,l,mid)Up(i,1);//把l到mid的操作加入
ll cnt=0,nowp=1;
for(auto p:que[x])a[++cnt]=p;//将询问按r从小到大排序
sort(a+1,a+cnt+1,cmp1);
while(nowp<=cnt&&a[nowp].r==mid)nowp++;//r=mid的询问肯定跟右边的修改无关啦
FOR(i,mid+1,r)
{
Up(i,1);//加入当前修改
if(i==mid+1)T.reset(1);//1到mid的修改操作不应该求历史最大值
while(a[nowp].r==i&&nowp<=cnt){ans[a[nowp].id]=max(ans[a[nowp].id],T.query(1,n,1,a[nowp].x,a[nowp].y));nowp++;}//正常的询问
}
REP(i,r,mid+1)Up(i,-1);//回退
if(l!=r)Solve(rs,mid+1,r);//遍历右子树
cnt=0,nowp=1;
for(auto p:que[x])a[++cnt]=p;
sort(a+1,a+cnt+1,cmp2);//将询问按l从大到小排序
while(nowp<=cnt&&a[nowp].l==mid+1)nowp++;//同上
REP(i,mid,l)
{
if(i==mid)T.reset(1);
while(a[nowp].l==i&&nowp<=cnt){ans[a[nowp].id]=max(ans[a[nowp].id],T.query(1,n,1,a[nowp].x,a[nowp].y));nowp++;}
Up(i,-1);
}//同上
if(l!=r)Solve(ls,l,mid);//遍历左子树
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=read(),m=read(),q=read();
FOR(i,1,m)
{
ll l=read(),x=read(),r=read(),y=read(),val=read();
v[l].pb((node){x,y,val});
v[r+1].pb((node){x,y,-val});
}
FOR(i,1,q)
{
ll l=read(),x=read(),r=read(),y=read();
add(1,1,n,(ask){(ll)i,l,r,x,y});
}
FOR(i,1,n)sort(v[i].begin(),v[i].end(),cmp);
Solve(1,1,n);
FOR(i,1,q)printf("%lld\n",ans[i]);
return 0;
}
如果你觉得这篇题解对你有帮助,那你可以点个赞支持我一下qwq。如果你对题解有任何问题/认为我的题解有任何问题,可以私信/在评论区发出来,当然如果你对我的题解有任何意见/建议也欢迎指出。我会尽我全力把我题解写到最好的qwq