线段树水题
[THUSCH2017] 大魔法师
给定 \(n\) 个三元组 \((A,B,C)\) 。共有 \(m\) 种区间操作,分为三大类,七小类。
1.\(A_i=A_i+B_i\) 2.\(B_i=B_i+C_i\) 3.\(C_i=C_i+A_i\)
给定值 \(v\) 4. \(A_i=A_i+v\) 5. \(B_i=B_i\times v\) 6. \(C_i=v\)
7.区间查询所有三元组的和对 \(998244353\) 取模的结果。
\(n,m\le 2.5\times10^5\)
直接建立线段树,每个节点上改为维护矩阵。将六种修改操作分别建出转移矩阵即可,需要支持区间乘法。卡常可以使用矩阵乘法循环展开和取模优化。
[NOI2016] 区间
给定 \(n\) 个区间,要从中选出 \(m\) 个区间,使得它们共同包含至少一个位置。对于一个选取方案,其花费为选中的最长区间长度减去最短区间长度。求最小权值,若不存在合法方案输出 \(-1\) 。
\(n\le 5\times 10^5\;\;m\le 2\times 10^5\;\;0\le l\le r\le 10^9\)
我们可以先对区间长度排个序。然后我们依次将区间加入,利用双指针,如果存在某个位置出现次数大于等于 \(m\) ,我们再不断删去区间,同时不断更新答案。然后我们考虑如何去判断某个位置出现次数大于等于 \(m\) ,我们先将所有区间离散化,然后扔到一棵线段树上,记录区间最大值,维护区间加的操作即可。时间复杂度为 \(O(n\log n)\) 。
[HEOI2013] Segment
[JSOI2008] Blue Mary 开公司
\(n\) 个操作,包括:加入一条两端点为点 \((x_0,y_0),(x_1,y_1)\) 的线段,询问当 \(x=k\) 时纵坐标最大的那个线段的编号。强制在线。
\(n\le 10^5\)
李超线段树模板题。我们维护区间中的最优线段,我们取当 \(x=mid\) 时值更大的线段为该区间的最优线段,然后我们通过比较 \(l\) 处和 \(r\) 处的值大小,递归地去更新左右区间。注意李超线段树不存在 \(pushup\) 和 \(pushdown\) 等操作。询问的时候直接在树上递归取区间最大值即可,注意一定要递归去取最大值。时间复杂度为 \(O(n\log ^2n)\) 。
以下代码以 [HEOI2013] Segment 为例。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=1e5+9,mod=39989,mod2=1e9,INF=2e9;
const double eps=1e-10;
typedef long long int ll;
typedef pair <double,int> PDI;
struct seg{
double k,b;
int l,r;
} p[N];
struct node{
int maxx;
} tr[N<<2];
int n,last,tot;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0' && c<='9') {x=x*10+c-48,c=getchar();}
return x*f;
}
inline double get(int id,int x)
{
if(x<p[id].l || x>p[id].r) return -INF;//不在区间内
return p[id].k*x+p[id].b;
}
void upd(int k,int l,int r,int L,int R,int id)
{
if(R<l || r<L) return;
if(L<=l && r<=R)
{
if(tr[k].maxx==0)
{
tr[k].maxx=id;
return;
}
int mid=(l+r)>>1;
if(get(id,mid)-get(tr[k].maxx,mid)>eps) swap(id,tr[k].maxx);//mid处值较大的作为区间代表
if((get(id,l)-get(tr[k].maxx,l)>eps) || (get(id,l)==get(tr[k].maxx,l) && id<tr[k].maxx)) upd(k<<1,l,mid,L,R,id);
if((get(id,r)-get(tr[k].maxx,r)>eps) || (get(id,r)==get(tr[k].maxx,r) && id<tr[k].maxx)) upd(k<<1|1,mid+1,r,L,R,id);
return;
}
int mid=(l+r)>>1;
upd(k<<1,l,mid,L,R,id),upd(k<<1|1,mid+1,r,L,R,id);
}
PDI maxx(PDI a,PDI b)
{
if(a.first-b.first>eps) return a;
else if(b.first-a.first>eps) return b;
else if(a.second<b.second) return a;
return b;
}
PDI query(int k,int l,int r,int pos)
{
PDI ans;
if(tr[k].maxx!=0) ans={get(tr[k].maxx,pos),tr[k].maxx};
if(l==r) return ans;
int mid=(l+r)>>1;
if(pos<=mid) ans=maxx(ans,query(k<<1,l,mid,pos));
else ans=maxx(ans,query(k<<1|1,mid+1,r,pos));
return ans;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
int opt=read();
if(opt==0)
{
int k=read();
k=(k+last-1)%mod+1;
last=query(1,1,mod,k).second;
printf("%d\n",last);
}
else
{
int x0=read(),y0=read(),x1=read(),y1=read();
x0=(x0+last-1)%mod+1,x1=(x1+last-1)%mod+1,y0=(y0+last-1)%mod2+1,y1=(y1+last-1)%mod2+1;
++tot;
if(x0==x1)
p[tot].k=0,p[tot].b=max(y0,y1),p[tot].l=p[tot].r=x0;
else
{
p[tot].k=(y1-y0)*1.0/(x1-x0);
p[tot].b=y0-p[tot].k*x0;
p[tot].l=min(x0,x1),p[tot].r=max(x0,x1);
}
upd(1,1,mod,min(x0,x1),max(x0,x1),tot);
}
}
return 0;
}
[SDOI2016]游戏
给定一棵 \(n\) 个点的带边权树。每个节点上有若干个数字,初始每个节点上为正无穷。有 \(Q\) 次操作,一是在 \(s\) 到 \(t\) 路径上的所有点 \(u\) 上添加一个值为 \(a\times dist_{u,s}+b\) 的数字,二是查询 \(s\) 到 \(t\) 路径上所有点上的数字的最小值。
\(n\le 10^5\;\;Q\le 10^5\)
我们对 \(a\times dist_{u,s}+b\) 这个式子进行一个拆解。令 \(LCA(s,t)=p\) ,\(dist_i\) 表示从根节点到点 \(i\) 的距离。则在 \(s\) 到 \(p\) 路径上的所有点 \(i\) ,插入了值
在 \(p\) 到 \(t\) 路径上的所有点 \(i\) ,插入了值
发现就是加了两条线段,所以我们用李超线段树去维护,同时还要维护一个区间最小值。而对于树上路径我们直接用树剖处理即可。时间复杂度为 \(O(n\log ^3n )\) 。