区间更新加区间查询模板
题目POJ3468
ac代码
#include<iostream>
#define ls idx<<1
#define rs idx<<1|1
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll sum[maxn<<2],tag[maxn<<2];
int num[maxn];
inline void pushup(ll idx)
{
sum[idx]=sum[ls]+sum[rs];
}
inline void pushdown(ll idx,ll l,ll r)
{
ll mid=l+r>>1;
tag[ls]+=tag[idx];
sum[ls]+=tag[idx]*(mid-l+1);
tag[rs]+=tag[idx];
sum[rs]+=tag[idx]*(r-mid);
tag[idx]=0;
}
void buildtree(ll idx,ll l,ll r)
{
if(l==r)
{
sum[idx]=num[l];
tag[idx]=0;
return;
}
ll mid=l+r>>1;
buildtree(ls,l,mid);
buildtree(rs,mid+1,r);
pushup(idx);
}
ll query(ll idx,ll l,ll r,const ll& x,const ll& y)
{
if(x<=l && y>=r)
return sum[idx];
if(tag[idx])
pushdown(idx,l,r);
ll res=0;
ll mid=l+r>>1;
if(x<=mid)
res+=query(ls,l,mid,x,y);
if(y>mid)
res+=query(rs,mid+1,r,x,y);
return res;
}
//区间加
void update(ll idx,ll l,ll r,const ll &x,const ll &y,const ll& val)
{
if(x<=l && y>=r)
{
sum[idx]+=val*(r-l+1);
tag[idx]+=val;
return;
}
if(tag[idx])
pushdown(idx,l,r);
ll mid=l+r>>1;
if(x<=mid)
update(ls,l,mid,x,y,val);
if(y>mid)
update(rs,mid+1,r,x,y,val);
pushup(idx);
}
//多样里时记得初始化
void init()
{
memset(tag,0,sizeof(tag));
}
int main()
{
ll N,M;
scanf("%lld%lld",&N,&M);
for (ll i = 1; i <= N; i++)
scanf("%d", num + i);
buildtree(1,1,N);
while (M--){
char c;
//cin>>c;
getchar();
scanf("%c",&c);
//求和指令
if (c == 'Q'){
ll xx, yy;
scanf("%lld%lld", &xx, &yy);
ll ans=query(1,1,N,xx, yy);
printf("%lld\n", ans);
}
//区间更新
else if (c == 'C'){
ll xx, yy, zz;
scanf("%lld%lld%lld", &xx, &yy, &zz);
update(1,1,N,xx, yy, zz);
}
}
return 0;
}
线段树+离散化
题目:POJ2528
题意和题解
题意:
给出一面墙,给出n张海报贴在墙上,每张海报都覆盖一个范围,问最后可以看到多少张海报
题解
海报覆盖的范围很大,直接使用数组存不下,
但是只有最多10000张海报,也就是说最多出现20000个点,所以可以使用离散化,
将每个点离散后,重新对给出控制的区间,这样区间最大就是1到20000.
可以直接使用线段树,成段更新,每次更新一个颜色,最后遍历所有的段,将还可以遍历的颜色储存,统计。
线段的离散化,要在两个差值大于1节点间,多加一个节点,代表这两点不连续。
但是这题数据有问题,没有加隔离点的离散化也可以过。
如 1 3 1 10 1 4 7 10 这种组样例
正确结果应该是还可以看到3种颜色,但是如果直接排列点的话,不在距离大于1的点直接加如间隔点,
就会挤掉5到6这一种颜色,导致只能看到两种颜色。这显然是错误的离散化,但依然可以通过这题。
真正的这种成段的离散化,一定要将两个差值大于1节点中,多加一个节点,代表这两点不连续。
AC代码
/*
*/
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e5 + 5;
struct seg
{
int l, r;
seg(int L = 0, int R = 0)
{
l = L;
r = R;
}
} Seg[maxn];
int num[maxn << 2];
bool visit[maxn << 1];
int cou;
struct node
{
int l, r, v;
node(int L = 0, int R = 0, int V = 0)
{
l = L, r = R, v = V;
}
} Node[maxn * 8];
void butre(int l, int r, int idx)
{
Node[idx] = node(l, r);
if (l == r)
return;
int mid = l + r >> 1;
butre(l, mid, idx << 1);
butre(mid + 1, r, idx << 1 | 1);
}
void qurry(int l, int r, int idx)
{
if (Node[idx].v)
{
//cout<<l<<" oo "<<r<<" oo "<<Node[idx].v<<endl;
if (!visit[Node[idx].v])
cou++;
visit[Node[idx].v] = true;
return;
}
if (l == r)
return;
int mid = l + r >> 1;
qurry(l, mid, idx << 1);
qurry(mid + 1, r, idx << 1 | 1);
}
void pushdown(int idx)
{
Node[idx << 1 | 1].v = Node[idx << 1].v = Node[idx].v;
Node[idx].v = 0;
}
void update(int l, int r, int idx, int X)
{
int L = Node[idx].l, R = Node[idx].r;
if (l == L && r == R)
{
Node[idx].v = X;
return;
}
if (Node[idx].v)
pushdown(idx);
int mid = L + R >> 1;
if (l <= mid && r > mid)
{
update(l, mid, idx << 1, X);
update(mid + 1, r, idx << 1 | 1, X);
}
else if (r <= mid)
update(l, r, idx << 1, X);
else if (l > mid)
update(l, r, idx << 1 | 1, X);
}
int main()
{
int t;
cin >> t;
while (t--)
{
memset(visit, 0, sizeof(visit));
cou = 0;
int cnt = 0;
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int l, r;
cin >> l >> r;
num[++cnt] = l;
num[++cnt] = r;
num[++cnt] = l + 1;
Seg[i] = seg(l, r);
}
sort(num + 1, num + cnt + 1);
int ncnt = cnt;
//加入间隔点
for (int i = 1; i < cnt; i++)
if (num[i + 1] - num[i] > 1)
num[++ncnt] = num[i] + 1;
cnt = ncnt;
sort(num + 1, num + 1 + cnt);
cnt = unique(num + 1, num + cnt + 1) - num - 1;
butre(1, cnt, 1);
for (int i = 1; i <= n; i++)
{
int L = lower_bound(num + 1, num + cnt + 1, Seg[i].l) - num;
int R = lower_bound(num + 1, num + cnt + 1, Seg[i].r) - num;
update(L, R, 1, i);
}
qurry(1, cnt, 1);
cout << cou << endl;
}
return 0;
}
线段树上二分查询
题目POJ2828
题意+题解
题意:
给出n个人入队位置,输入一个idx,num
代表num需要插入队列中第idx个人后面
请输出所有入队后的队列情况
题解:
对于第n个人而言,他的位置是可以直接确定的,即idx[n]+1,
而对于第n-1个人,假设前n-2个人的位置已经排好,然后把他放在了idx[n-1]+1的位置,
他位置只可能会被第n个人影响,即当第n个人插入再第n-1个人之前时,他的位置编号变成了idx[n-1]+2
同理对于第n-2个人,假设前n-3的位置已经确定,然后把他放在了idx[n-2]+1的位置,
那么的他编号只可能因为第n-1个人或者第n个插入在idx[n-2]+1之前时发生改变
总结来说:任意一个人的下标只可能会被在他后面插入的人而改变。
对于最终的状态,即所有人都已经在对应的位置上,设此时第i个人的位置是loc[i]
可以发现,idx[i]+1=loc[i]-(编号为i+1~n的人中,最终排第i个人前面的人的人数)
那么,对编号小于等于i的人设他的贡献为1,对于编号大于i的人设他的贡献0,
可以用一个数列将最终队列状态中,对于第i个人的贡献状态表示出来
前缀和等于idx[i]+1的位置即为loc[i]
通过前面的讨论,可以知道,对于第n个人所有人都是有贡献的,其实即为一个全1的数列,
找到前缀和为idx[n]+1的位置,
然后将idx[n]+1这个位置的贡献置为0,去讨论第n-1个人,找到前缀和为idx[n-1]+1的位置,
重复上述过程
可以通过线段树维护一个初始全为1的数列,然后不断进行单点修改以及二分查询前缀和模拟操作,
然而因为这两个操作都是同一个位置,所以直接合并
AC代码
/*
*/
#include<iostream>
#include<stdio.h>
#define ls idx<<1
#define rs idx<<1|1
using namespace std;
const int maxn=2e5+5;
int ans[maxn];
int sum[maxn<<2];
inline void pushup(int idx) {sum[idx]=sum[rs]+sum[ls];}
void build(int idx,int l,int r)
{
if(l==r)
{
sum[idx]=1;
return ;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(idx);
//cout<<idx<<' '<<l<<' '<<r<<'\n';
}
int query_change(int idx,int l,int r,int num)
{
if(l==r)
{
sum[idx]=0;
return l;
}
int res;
int mid=l+r>>1;
if(sum[ls]>=num)
res=query_change(ls,l,mid,num);
else
res=query_change(rs,mid+1,r,num-sum[ls]);
pushup(idx);
return res;
}
int id[maxn],Num[maxn];
int main()
{
//std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;
while (~scanf("%d",&n))
{
//cout<<n<<endl;
build(1,1,n);
for(int i=1;i<=n;i++)
scanf("%d%d",id+i,Num+i);
for(int i=n;i>=1;i--)
ans[query_change(1,1,n,id[i]+1)]=Num[i];
for(int i=1;i<n;i++)
printf("%d ",ans[i]);
printf("%d\n",ans[n]);
}
return 0;
}
线段树上找答案
题目HDU1540
题意+题解
题意:
给出n个一排的城市,初始相邻城市相互连接
有3个操作
D x:损坏城市x的管道,使得他与相邻城市不相连
Q x:查询包含x城市的最长连续城市段
R :修复最近损坏的一个城市的管道
题解:
用0表示损坏,1表示为损坏。那么问题转化为k位置所在最长‘1’串
考虑用线段树维护区间最长连续1串
那么只需要维护每个区间的前缀连续1,后缀连续1,以及最长连续1,即可实现区间合并
那么我们怎么知道,该区间的最长连续1是否包含了位置k呢
注意到任意一个区间的最长连续串1,必定是由某些子区间的前缀1和后缀1拼接得到的
这就给查询提供了思路。
当查询到k落于该区间的[mid-pre[ls]+1,mid+pre[rs]]这段区间时,将这段区间返回即可
否则继续查询包含k的子区间
经过上述讨论发现,查询k只需要知道每个区间的前缀1,和后缀1即可
前缀1和后缀1的合并方法也很显然,画图即可知,具体见实现
修改就是单点修改
AC代码
/*
*/
#include <bits/stdc++.h>
#define ls idx << 1
#define rs idx << 1 | 1
using namespace std;
const int maxn = 5e4 + 5;
int pre[maxn << 2], suf[maxn << 2];
void pushup(int idx, int l, int r)
{
pre[idx] = pre[ls];
suf[idx] = suf[rs];
int mid = l + r >> 1;
if (pre[ls] == mid - l + 1)
pre[idx] += pre[rs];
if (suf[rs] == r - mid)
suf[idx] += suf[ls];
}
void build(int idx, int l, int r)
{
if (l == r)
{
pre[idx] = suf[idx] = 1;
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(idx, l, r);
}
int query(int idx, int l, int r, int k)
{
if (l == r)
return pre[idx];
int mid = l + r >> 1;
if (k >= mid - suf[ls] + 1 && k <= mid + pre[rs])
return suf[ls] + pre[rs];
else if (k <= mid)
return query(ls, l, mid, k);
else
return query(rs, mid + 1, r, k);
}
void update(int idx, int l, int r, const int x, const int val)
{
if (l == r && l == x)
{
pre[idx] = suf[idx] = val;
return;
}
int mid = l + r >> 1;
if (x <= mid)
update(ls, l, mid, x, val);
else
update(rs, mid + 1, r, x, val);
pushup(idx, l, r);
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
stack<int> ST;
while (cin >> n >> m)
{
build(1, 1, n);
while (!ST.empty())
ST.pop();
while (m--)
{
char c;
int x;
cin >> c;
if (c == 'D')
{
cin >> x;
ST.push(x);
update(1, 1, n, x, 0);
}
else if (c == 'Q')
{
cin >> x;
cout << query(1, 1, n, x) << '\n';
}
else
{
if (ST.size())
{
update(1, 1, n, ST.top(), 1);
ST.pop();
}
}
}
}
return 0;
}
线段树维护树上信息
题目:POJ3321
题意+题解
题意:
给你一棵树,每个节点开始的时候有一个苹果,
下边m个操作,Q a,查询以a和a的子树的总共的苹果数,
c b,修改操作,改变b节点的苹果状态,有变没有,没有变成有
题解:
通过求每个节点的DFS序得出每棵子树的区间表示法,
然后通过线段树/树状数组进行单点修改和区间查询即可
AC代码
/*
*/
#include<iostream>
#include<vector>
#define ls idx<<1
#define rs idx<<1|1
using namespace std;
const int maxn=1e5+5;
int L[maxn],R[maxn];
vector<vector<int> > E(maxn);
int tot;
void dfs(int fa,int x)
{
L[x]=++tot;
for(int i=0;i<E[x].size();i++)
{
int v=E[x][i];
if(v!=fa)
dfs(x,v);
}
R[x]=tot;
}
int sum[maxn<<2];
inline void pushup(int idx)
{
sum[idx]=sum[ls]+sum[rs];
}
void build(int idx,int l,int r)
{
if(l==r)
{
sum[idx]=1;
return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(idx);
}
void change(int idx,int l,int r,int x)
{
if(l==r && l==x)
{
sum[idx]^=1;
return;
}
int mid=l+r>>1;
if(x<=mid)
change(ls,l,mid,x);
else
change(rs,mid+1,r,x);
pushup(idx);
}
int query(int idx,int l,int r,const int x,const int y)
{
if(x<=l && y>=r)
return sum[idx];
int mid=l+r>>1;
int res=0;
if(x<=mid)
res+=query(ls,l,mid,x,y);
if(y>mid)
res+=query(rs,mid+1,r,x,y);
return res;
}
int main()
{
int n;
//std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
while(~scanf("%d",&n)){
tot=0;
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
E[x].push_back(y);
E[y].push_back(x);
}
dfs(1,1);
build(1,1,tot);
int Q;
scanf("%d",&Q);
while (Q--)
{
getchar();
char c;
int xx;
scanf("%c %d",&c,&xx);
if(c=='Q')
cout<<query(1,1,tot,L[xx],R[xx])<<'\n';
else
change(1,1,tot,L[xx]);
}
for(int i=1;i<=n;i++)
E[i].clear();
}
return 0;
}
线段树优化建图
题目:CF7868-legacy
题意+题解
题意:
有n个点, q次操作, 操作有三种:
1 u v w 表示u -> v 有有一条权值为w的边.
2 u l r w 表示 u 到 下标在[l, r] 的城市 有一条权值为w的边
3 u l r w 表示 下标在[l, r] 的城市 到 u 有一条权值为w的边
然后求给出起点s, 输出s到每一个点的最小花费. 不能到达输出-1.
思路:
很明显直接暴力建边, 建的过程就会T, 边数也非常多.
但是,我们可以发现连的城市是一段连续区间,
所以我们需要利用这个特性, 那么就很明显想到线段树区间分层问题.
这样我们的建图的边数最多只有nlogn条边, 建边的过程也优化了.
具体操作过程为: 我们需要两颗线段树. 第一颗叫入树, 第二颗叫出树,
入树的每段区间都向左右儿子连一条权值为0的边, 叶子节点即为原来的点,
然后出树相反, 是左右儿子向父节点连一条权值为0的边, 叶子结点与入树共用. 都是有向边.
然后对于操作, 1操作直接连点加权边,
2操作u 向 入树对应区间的所有标号连一条权值为w的有向边,
3操作 出树的所有区间标号向u连一条权值为w的有向边.
这样就优化完成了, 并且符合要求, 画出来就很明显可以看出来了
最后跑堆优化的迪杰斯特拉即可得到答案
AC代码
/*
*/
#include <bits/stdc++.h>
#define ls idx << 1
#define rs idx << 1 | 1
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int val[2][maxn << 2]; //每个区间的都是一个点,这是它们的编号
int cnt_ponit;
struct edge
{
ll to, v;
bool operator <(const edge &A) const
{
return v > A.v;
}
};
vector<edge> E[maxn << 4];
void build(int idx, int l, int r, int flag)
{
if (l == r)
{
val[flag][idx] = l;
return;
}
val[flag][idx] = ++cnt_ponit;
int mid = l + r >> 1;
build(ls, l, mid, flag);
build(rs, mid + 1, r, flag);
if (!flag)
{
E[val[flag][idx]].push_back({val[flag][ls], 0});
E[val[flag][idx]].push_back({val[flag][rs], 0});
}
else
{
E[val[flag][ls]].push_back({val[flag][idx], 0});
E[val[flag][rs]].push_back({val[flag][idx], 0});
}
}
//z向[x,y]区间连线(或者反向),权值为w,flag=0正向,flag=1反向
void query(int idx, int l, int r, const int x, const int y, const int z, const int w, const int flag)
{
if (x <= l && y >= r)
{
if (!flag)
E[z].push_back({val[flag][idx], w});
else
E[val[flag][idx]].push_back({z, w});
return;
}
int mid = l + r >> 1;
if (x <= mid)
query(ls, l, mid, x, y, z, w, flag);
if (y > mid)
query(rs, mid + 1, r, x, y, z, w, flag);
}
priority_queue<edge> Q;
ll dis[maxn << 4];
bool vis[maxn << 4];
void DJ(int star)
{
dis[star]=0;
Q.push({star, 0});
while (!Q.empty())
{
int now = Q.top().to;
Q.pop();
if (vis[now])
continue;
vis[now] = 1;
for (int i = 0; i < E[now].size(); i++)
{
int to = E[now][i].to, v = E[now][i].v;
if (dis[to] > dis[now] + v)
{
dis[to] = dis[now] + v;
Q.push({to, dis[to]});
}
}
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, q, s;
cin >> n >> q >> s;
cnt_ponit = n;
build(1, 1, n, 0);
build(1, 1, n, 1);
fill(dis+1, dis + cnt_ponit+1, 1e18);
while (q--)
{
int op;
cin >> op;
int xx, yy, zz, ww;
if (op == 1)
{
cin >> xx >> yy >> zz;
E[xx].push_back({yy, zz});
}
else if (op == 2 || 3)
{
cin >> zz >> xx >> yy >> ww;
query(1, 1, n, xx, yy, zz, ww, op - 2);
}
}
for(int i=1;i<=cnt_ponit;i++)
{
E[i].push_back({i,0});
}
DJ(s);
for (int i = 1; i <= n; i++)
{
if (dis[i] < 1e18)
cout << dis[i] << ' ';
else
cout << -1 << ' ';
}
return 0;
}
线段树离线查询区间种类数
题目HDU3333
题意+题解
题意:
给出n个数a1,a2…an,
Q个询问,每个询问给出l,r
求区间[al~ar]出现过的数的种类和
题解:
线段树离线处理询问。
预处理处理出每个数上一次(数组中当前下标往前)出现的位置,
用lastidx[i]表示a[i]上一次出现的位置
对于每个询问(l,r),if(lastidx[i]<l)则a[i]是第一次在此区间出现,否则不是
把a[i]按lastidx[i]进行排序;
对所有区间按l进行排序,每次把lastidx[i]<l的a[i]加入线段树中,
然后询问线段树中区间(l,r)之和即可
AC代码
/*
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int maxm = maxn << 2;
typedef long long ll;
//当前结点左右区间,左右孩子的下标,保存的区间和
int l[maxm], r[maxm], ls[maxm], rs[maxm];
ll val[maxm];
struct questions
{
int l, r, id;
};
vector<questions> ques;
int a[maxn], lastidx[maxn];
int a_idx[maxn];
map<int, int> lef;
int n;
bool cmp(int aa, int bb)
{
return lastidx[aa] < lastidx[bb];
}
bool cmp2(questions A, questions B)
{
return A.l < B.l;
}
void pushup(int idx)
{
val[idx] = val[rs[idx]] + val[ls[idx]];
}
void creat_tree(int idx, int x, int y)
{
ls[idx] = idx<<1;
rs[idx] = idx<<1|1;
l[idx] = x, r[idx] = y;
if (x == y)
{
val[idx] = 0;
return;
}
int mid = x + y >> 1;
creat_tree(ls[idx], x, mid);
creat_tree(rs[idx], mid + 1, y);
pushup(idx);
}
void add(int idx, const int &x)
{
if (l[idx] == x && r[idx] == x)
{
val[idx] += a[x];
return;
}
int mid = l[idx] + r[idx] >> 1;
if (x <= mid)
add(ls[idx], x);
else
add(rs[idx], x);
pushup(idx);
}
ll query(int idx, const int x, const int y)
{
if (x<=l[idx] && y >= r[idx])
return val[idx];
int mid = r[idx] + l[idx] >> 1;
ll ans = 0;
if (x <= mid)
ans += query(ls[idx], x, y);
if (y > mid)
ans += query(rs[idx], x, y);
return ans;
}
void init()
{
for (int i = 1; i <= n; i++)
a_idx[i] = i;
ques.clear();
lef.clear();
for (int i = 1; i <= n; i++)
{
if (!lef.count(a[i]))
lastidx[i] = 0;
else
lastidx[i] = lef[a[i]];
lef[a[i]] = i;
}
}
ll ans[maxn];
void sovle()
{
sort(a_idx + 1, a_idx + 1 + n, cmp);//把a数组原来的下标按照lastidx[i]排序
sort(ques.begin(), ques.end(),cmp2);
int now=1;
for(auto it : ques)
{
while(lastidx[a_idx[now]]<it.l && now<=n)
{
add(1,a_idx[now]);
now++;
}
ans[it.id]=query(1,it.l,it.r);
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;
cin >> T;
while (T--)
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
init();
creat_tree(1,1,n);
int Q;
cin >> Q;
for (int i = 1; i <= Q; i++)
{
int x, y;
cin >> x >> y;
ques.push_back({x, y, i});
}
sovle();
for(int i=1;i<=Q;i++)
cout<<ans[i]<<'\n';
//cout<<ans[Q]<<'\n';
}
return 0;
}
线段树维护哈希值
题目大意:给出一个长度为 n 的序列,接下来有 m 次操作,每次操作分为下列两种类型:
1 l r:区间 [ l , r ] 内的所有数都加 1 并对 65536 取模,也就是 i ∈ [ l , r ] ,有 a[ i ] =( a[ i ] + 1 ) % 65536
2 x y len:查询两段区间 [ x , x + len - 1 ] 和 [ y , y + len - 1 ] 内的序列是否相同
#include <bits/stdc++.h>
#define ls idx << 1
#define rs idx << 1 | 1
using namespace std;
typedef long long ll;
const ll maxn = 5e5 + 5, base = 10, mod = 1e9 + 7, mod2 = 65536;
ll tree[maxn << 2], L[maxn << 2], R[maxn << 2], mx[maxn << 2], tag[maxn << 2];
ll bpow[maxn]; //base的幂次
ll a[maxn];
ll sump[maxn]; //base幂次的前缀和
void pushup(ll idx)
{
tree[idx] = (tree[rs] + (tree[ls] * bpow[R[rs] - L[rs] + 1])) % mod;
mx[idx] = max(mx[ls], mx[rs]);
}
void pushdown(ll idx)
{
tree[ls] = (tree[ls] + (sump[R[ls] - L[ls]]) * tag[idx]) % mod;
tree[rs] = (tree[rs] + (sump[R[rs] - L[rs]]) * tag[idx]) % mod;
tag[ls] = (tag[ls] + tag[idx]) % mod;
tag[rs] = (tag[rs] + tag[idx]) % mod;
mx[ls] = (mx[ls] + tag[idx]) % mod;
mx[rs] = (mx[rs] + tag[idx]) % mod;
tag[idx] = 0;
}
void build(ll idx, ll l, ll r)
{
L[idx] = l, R[idx] = r;
tag[idx] = 0;
if (l == r)
{
mx[idx] = tree[idx] = a[l];
return;
}
ll mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(idx);
}
void update(ll idx, const ll &x, const ll &y, const ll &val)
{
if (x <= L[idx] && y >= R[idx])
{
tag[idx] = (tag[idx] + val) % mod;
tree[idx] = (tree[idx] + (sump[R[idx] - L[idx]])) % mod;
mx[idx] = ll(mx[idx] + val) % mod;
return;
}
if (tag[idx])
pushdown(idx);
ll mid = L[idx] + R[idx] >> 1;
if (x <= mid)
update(ls, x, y, val);
if (y > mid)
update(rs, x, y, val);
pushup(idx);
}
void modify(ll idx)
{
if (L[idx] == R[idx])
{
mx[idx] %= mod2;
tree[idx] %= mod2;
return;
}
if (tag[idx])
pushdown(idx);
if (mx[ls] >= mod2)
modify(ls);
if (mx[rs] >= mod2)
modify(rs);
pushup(idx);
}
ll query(ll idx, ll x, ll y)
{
if (x <= L[idx] && y >= R[idx])
return tree[idx] * bpow[(y - R[idx])];
if (tag[idx])
pushdown(idx);
ll mid = L[idx] + R[idx] >> 1;
ll ans = 0;
if (x <= mid)
ans += query(ls, x, y);
if (y > mid)
ans += query(rs, x, y);
return ans % mod;
}
void init()
{
bpow[0] = 1;
for (ll i = 1; i < maxn; i++)
bpow[i] = (bpow[i - 1] * base) % mod;
sump[0] = 1;
for (ll i = 1; i < maxn; i++)
sump[i] = (sump[i - 1] + bpow[i]) % mod;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
init();
ll n, q;
cin >> n >> q;
for (ll i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
while (q--)
{
ll tp, x, y, LL;
cin >> tp;
if (tp == 1)
{
cin >> x >> y;
update(1, x, y, 1);
modify(1);
}
else
{
cin >> x >> y >> LL;
//cout << query(1, x, x + LL - 1) << " q " << query(1, y, y + LL - 1) << '\n';
if (query(1, x, x + LL - 1) == query(1, y, y + LL - 1))
cout << "yes\n";
else
cout << "no\n";
}
}
return 0;
}
动态开点线段树求逆序对
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
const int inf = 1e9 + 5;
int a[maxn];
int tot = 1;
//查询逆序对只需要查询大于他的数的个数
struct node
{
int cnt, ls, rs;
} tree[maxn << 4];
void pushup(int rt)
{
tree[rt].cnt = tree[tree[rt].ls].cnt + tree[tree[rt].rs].cnt;
}
void insert(int &rt, int l, int r, int val)
{
//cout << l << ' ' << r << ' ' << val << " ins\n";
if (!rt)
rt = ++tot;
if (l == r)
{
tree[rt].cnt++;
return;
}
int mid = l + r >> 1;
if (val <= mid)
insert(tree[rt].ls, l, mid, val);
else
insert(tree[rt].rs, mid + 1, r, val);
pushup(rt);
}
int query(int rt, int l, int r, int x, int y)
{
if (!rt)
return 0;
if (x <= l && y >= r)
return tree[rt].cnt;
int mid = l + r >> 1;
int ans = 0;
if (x <= mid)
ans += query(tree[rt].ls, l, mid, x, y);
if (y > mid)
ans += query(tree[rt].rs, mid + 1, r, x, y);
return ans;
}
int main()
{
int n;
cin >> n;
int root = 1;
long long res=0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
res+=query(root, 1, inf, a[i] + 1, inf);
insert(root, 1, inf, a[i]);
}
cout<<res<<'\n';
return 0;
}