P3372 【模板】线段树 1
思路
线段树是一种完全二叉树,父节点的值是两个子结点的值之和
线段树可用于区间修改、区间查询、求最值等
实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
//将标记向下传递
void push_down(int p, int len)
{
if (len <= 1) return;
tree[p << 1] += mark[p] * (len - len / 2);
mark[p << 1] += mark[p];
tree[p << 1 | 1] += mark[p] * (len / 2);
mark[p << 1 | 1] += mark[p];
mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
{
//到达叶结点
if (cl == cr) return void(tree[p] = A[cl]);
int mid = (cl + cr) >> 1;
build(p << 1, cl, mid);
build(p << 1 | 1, mid + 1, cr);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
//区间包含
if (cl >= l && cr <= r) return tree[p];
//区间重叠但不包含,需要先将标记向下传递
push_down(p, cr - cl + 1);
ll mid = (cl + cr) >> 1, ans = 0;
if (mid >= l) ans += query(l, r, p << 1, cl, mid);
if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
//区间包含,直接更改该节点的值,更新标记
if (cl >= l && cr <= r)
{
tree[p] += d * (cr - cl + 1), mark[p] += d;
return ;
}
//区间重叠但不包含
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
if (mid >= l) update(l, r, d, p << 1, cl, mid);
if (mid < r) update(l, r, d, p << 1 | 1, mid + 1, cr);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> A[i];
build();
while (m--)
{
int o, l, r, d;
cin >> o >> l >> r;
if (o == 1)
cin >> d, update(l, r, d);
else
cout << query(l, r) << '\n';
}
return 0;
}
P3374 【模板】树状数组 1
思路
前缀和维护的区间和是[1,Ai],而树状数组维护的区间和是(Ai-lowbit(Ai),Ai]
树状数组常用于区间查询和单点修改
实现
#include <bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lowbit(x) ((x) & (-x))
int tree[MAXN];
int n,m;
inline void update(int i, int x)
{
for (int pos = i; pos <= n; pos += lowbit(pos))
tree[pos] += x;
}
inline int query(int n)
{
int ans = 0;
for (int pos = n; pos; pos -= lowbit(pos))
ans += tree[pos];
return ans;
}
inline int query(int a, int b)
{
return query(b) - query(a - 1);
}
int main()
{
cin>>n>>m;
int t;
memset(tree, 0, sizeof(tree));
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
update(i,t);
}
int a,b,c;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
if(a==1)
update(b,c);
else
printf("%d\n",query(b,c));
}
return 0;
}
P3368 【模板】树状数组 2
思路
树状数组用于区间修改和单点查询时,只要维护差分数组即可
实现
#include <bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lowbit(x) ((x) & (-x))
int tree[MAXN];
int a[MAXN];
int n,m;
inline void update(int i, int x)
{
for (int pos = i; pos <= n; pos += lowbit(pos))
tree[pos] += x;
}
inline int query(int n)
{
int ans = 0;
for (int pos = n; pos; pos -= lowbit(pos))
ans += tree[pos];
return ans;
}
int main()
{
cin>>n>>m;
int t;
memset(tree, 0, sizeof(tree));
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
int op;
int x,y,k;
for(int i=0;i<m;i++)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d%d%d",&x,&y,&k);
update(x,k);
update(y+1,-k);
}
else
{
scanf("%d",&k);
printf("%d\n",a[k]+query(k));
}
}
return 0;
}
P2574 XOR的艺术
思路
和线段树的模板题相比,仅仅是标记的使用方法和更新方式发生了变化
标记变为bool值,每次与1异或
而tree数组的更新则是当标记值为1时,tree[p] = len-tree[p]
实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 200005;
int tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
//将标记向下传递
void push_down(int p, int len)
{
if(!mark[p]) return ;
if (len <= 1) return ;
tree[p << 1] = (len - len / 2) - tree[p << 1];
mark[p << 1] ^= 1;
tree[p << 1 | 1] = (len / 2) - tree[p << 1 | 1];
mark[p << 1 | 1] ^= 1;
mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
{
//到达叶结点
if (cl == cr) return void(tree[p] = A[cl]);
int mid = (cl + cr) >> 1;
build(p << 1, cl, mid);
build(p << 1 | 1, mid + 1, cr);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
//区间包含
if (cl >= l && cr <= r) return tree[p];
//区间重叠但不包含,需要先将标记向下传递
push_down(p, cr - cl + 1);
ll mid = (cl + cr) >> 1, ans = 0;
if (mid >= l) ans += query(l, r, p << 1, cl, mid);
if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
return ans;
}
//[cl,cr]是当前树的区间,[l,r]是要操作的区间
void update(int l, int r,int p = 1, int cl = 1, int cr = n)
{
//区间包含,直接更改该节点的值,更新标记
if (cl >= l && cr <= r)
{
tree[p] = (cr - cl + 1) - tree[p], mark[p] ^= 1;
return ;
}
//区间重叠但不包含
if(mark[p])
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
if (mid >= l) update(l, r, p << 1, cl, mid);
if (mid < r) update(l, r, p << 1 | 1, mid + 1, cr);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int main()
{
// freopen("P2574_1.in","r",stdin);
// ios::sync_with_stdio(false);
scanf("%d %d\n",&n,&m);
char ch;
for (int i = 1; i <= n; ++i)
{
ch = getchar();
A[i] = ch - '0';
}
build();
while (m--)
{
int o, l, r;
scanf("%d%d%d",&o,&l,&r);
if (o == 0)
update(l, r);
else
cout << query(l, r) << '\n';
}
return 0;
}
P3373 【模板】线段树 2
思路
- 需要两个标记,一个代表加一个代表乘,同时也需要两个update函数
- push_down的时候注意
- 子结点的值 = 子结点的值 * 父结点的乘法标记 + 区间长度 * 父结点的加法标记
- 子结点的乘法标记 *= 父节点的乘法标记
- 子结点的加法标记 = 子结点的加法标记 * 父节点的乘法标记 + 子节点的加法标记
- 同时所有的计算最后需要模p
实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], mark2[MAXN << 2], n, m, P, A[MAXN];
//将标记向下传递
void push_down(int p, int len)
{
if (len <= 1) return;
tree[p << 1] = (tree[p << 1] * mark2[p] + mark[p] * (len - len / 2)) % P;
mark[p << 1] = (mark[p << 1] * mark2[p] + mark[p]) % P;
mark2[p << 1] *= mark2[p];
tree[p << 1 | 1] = (tree[p << 1 | 1] * mark2[p] + mark[p] * (len / 2)) % P;
mark[p << 1 | 1] = (mark[p << 1 | 1] * mark2[p] + mark[p]) % P;
mark2[p << 1 | 1] *= mark2[p];
mark2[p << 1] %= P;
mark2[p << 1 | 1] %= P;
mark[p] = 0;
mark2[p] = 1;
return ;
}
void build(int p = 1, int cl = 1, int cr = n)
{
//到达叶结点
if (cl == cr) return void(tree[p] = A[cl]);
int mid = (cl + cr) >> 1;
build(p << 1, cl, mid);
build(p << 1 | 1, mid + 1, cr);
tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % P;
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
//区间包含
if (cl >= l && cr <= r) return tree[p];
//区间重叠但不包含,需要先将标记向下传递
push_down(p, cr - cl + 1);
ll mid = (cl + cr) >> 1, ans = 0;
if (mid >= l) ans += query(l, r, p << 1, cl, mid);
if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
//区间包含,直接更改该节点的值,更新标记
if (cl >= l && cr <= r)
{
tree[p] = (tree[p] + d * (cr - cl + 1)) % P, mark[p] += d, mark[p] %= P;
return ;
}
//区间重叠但不包含
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
if (mid >= l) update(l, r, d, p << 1, cl, mid);
if (mid < r) update(l, r, d, p << 1 | 1, mid + 1, cr);
tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % P;
}
void update2(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
//区间包含,直接更改该节点的值,更新标记
if (cl >= l && cr <= r)
{
tree[p] *= d, mark2[p] *= d, mark[p] *= d, tree[p] %= P, mark[p] %= P, mark2[p] %= P;
return ;
}
//区间重叠但不包含
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
if (mid >= l) update2(l, r, d, p << 1, cl, mid);
if (mid < r) update2(l, r, d, p << 1 | 1, mid + 1, cr);
tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % P;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m >> P;
for (int i = 1; i <= n; ++i)
cin >> A[i];
build();
// cout<<"p: "<<P<<endl;
for(int i = 0; i <= 2*n; ++i)
mark2[i] = 1;
// for(int i = 0; i <= 2*n; ++i)
// cout<<tree[i]<<" ";
// cout<<endl;
while (m--)
{
int o, l, r, d;
cin >> o >> l >> r;
if (o == 1) //乘操作
cin >> d, update2(l, r, d);
else if(o==2) //加操作
cin >> d, update(l, r, d);
else
cout << query(l, r) % P << '\n';
}
return 0;
}
P1908 逆序对
思路
【算法2-3】分治中提到的解法是归并排序,这道题也可以使用离散化+树状数组来做
实现
//来自洛谷题解区 学无止境
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int tree[500010],ranks[500010],n;
long long ans;
struct point
{
int num,val;
}a[500010];
inline bool cmp(point q,point w)
{
if(q.val==w.val)
return q.num<w.num;
return q.val<w.val;
}
inline void insert(int p,int d)
{
for(;p<=n;p+=p&-p)
tree[p]+=d;
}
inline int query(int p)
{
int sum=0;
for(;p;p-=p&-p)
sum+=tree[p];
return sum;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i].val),a[i].num=i;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
ranks[a[i].num]=i;
for(int i=1;i<=n;i++)
{
insert(ranks[i],1);
ans+=i-query(ranks[i]);
}
printf("%lld",ans);
return 0;
}
P1966 [NOIP2013 提高组] 火柴排队
思路
两列火柴中,每个位置的火柴在所在列中的长度排名都相同时,
∑
(
a
i
−
b
i
)
2
\sum(a_i-b_i)^2
∑(ai−bi)2最小
转化为逆序对问题
实现
#include<bits/stdc++.h>
using namespace std;
typedef struct{
int v;
int k;
}node;
int n;
node a[100010],b[100010];
int x[100010],c[100010];
long long ans;
void msort(int b,int e)
{
if(b==e)
return;
int mid=(b+e)/2,i=b,j=mid+1,k=b;
msort(b,mid),msort(mid+1,e);
while(i<=mid&&j<=e)
{
if(x[i]<=x[j])
c[k++]=x[i++]; //c数组辅助归并排序
else //左边比右边大,产生逆序对
c[k++]=x[j++],ans= (ans+mid-i+1) % (99999997);
}
while(i<=mid) //如果左边没有放完就把左边区间所有剩下的数放进去
c[k++]=x[i++];
while(j<=e) //右边同理
c[k++]=x[j++];
for(int l=b;l<=e;l++) //更新a
x[l]=c[l];
}
bool cmp(node a,node b)
{
return a.v<b.v;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].v);
a[i].k = i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i].v);
b[i].k = i;
}
sort(a+1,a+1+n,cmp);
sort(b+1,b+1+n,cmp);
for(int i=1;i<=n;i++)
{
x[b[i].k] = a[i].k;
}
msort(1,n);
printf("%lld",ans%(99999997));
return 0;
}
P5677 [GZOI2017]配对统计
思路
预处理出所有好对+树状数组
洛谷题解区一篇优秀的题解
实现
#include <bits/stdc++.h>
using namespace std;
#define MAXN 300005
#define lowbit(x) ((x) & (-x))
typedef long long ll;
ll tree[MAXN];
int n,m;
struct query{
int l,r;
int index;
}querys[MAXN];
struct node{
int num;
int pos;
}a[MAXN];
struct goodpair{
int l,r;
}pairs[MAXN<<2];
int paircount;
void add_pair(int a,int b)
{
int i;
if(a>b) i = a,a = b,b = i;
pairs[++paircount].l = a;
pairs[paircount].r = b;
}
inline void update(int i, int x)
{
for (int pos = i; pos <= n; pos += lowbit(pos))
tree[pos] += 1;
}
inline ll Query(int n)
{
ll ans = 0;
for (int pos = n; pos; pos -= lowbit(pos))
ans += tree[pos];
return ans;
}
inline ll Query(int a, int b)
{
return Query(b) - Query(a - 1);
}
bool cmp(struct node a,struct node b)
{
return a.num<b.num;
}
bool cmp_pair(struct goodpair a,struct goodpair b)
{
if(a.r!=b.r) return a.r<b.r;
return a.l<b.l;
}
bool cmp_query(struct query a,struct query b)
{
if(a.r!=b.r) return a.r<b.r;
return a.l<b.l;
}
int main()
{
cin>>n>>m;
if(n==1)
{cout<<"0";
return 0;
}
int t;
memset(tree, 0, sizeof(tree));
for(int i = 1; i <= n; ++i)
{
scanf("%d",&t);
a[i].num = t;
a[i].pos = i;
}
sort(a+1,a+1+n,cmp);
for(int i = 1; i <= m; ++i)
{
scanf("%d%d",&querys[i].l,&querys[i].r);
querys[i].index = i;
}
sort(querys+1,querys+1+m,cmp_query);
if(n!=1)
{
add_pair(a[1].pos,a[2].pos);
add_pair(a[n].pos,a[n-1].pos);
}
for(int i=2;i<n;i++)
{
int d1 = a[i].num-a[i-1].num;
int d2 = a[i+1].num-a[i].num;
if(d1==d2) add_pair(a[i].pos,a[i-1].pos),add_pair(a[i+1].pos,a[i].pos);
else if(d1>d2) add_pair(a[i].pos,a[i+1].pos);
else add_pair(a[i].pos,a[i-1].pos);
}
sort(pairs+1,pairs+1+paircount,cmp_pair);
ll ans = 0;
for(int i=1,j=1;i<=m;i++)
{
while(pairs[j].r<=querys[i].r && j<=paircount)
{
update(pairs[j].l,1);
j++;
}
ans+=querys[i].index*(j-1-Query(querys[i].l-1));
}
printf("%lld",ans);
return 0;
}
这部分的题好难 不想再写剩下的了= =
欢迎指正-