数据结构|序列问题与树上问题小结
数据结构小结
好累啊这几天沉迷数据结构
高数被我鸽几天了,单词又背了遍abandon...
总结一下这几天沉迷的成果,这些东西虽然好玩,但是留给我的时间不多了,2020都过了好多天了
感觉要是不看爱情公寓,可以再多刷个两道题。。
珂朵莉树
之前写过了,https://www.cnblogs.com/fisherss/p/12182869.html
线段树常用模板
单点更新,区间更新,维护最值
https://www.cnblogs.com/fisherss/p/10920642.html
P3373 线段树维护区间乘、区间加
题解:https://www.luogu.com.cn/problemnew/solution/P3373
多个标记分清楚,标记的优先级顺序
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll mod;
ll a[maxn];
struct node{
ll v,mul,add;
}tree[maxn*4+10];
void pushup(int o){
tree[o].v = (tree[o<<1].v + tree[o<<1|1].v)%mod;
}
void pushdown(int o,int l,int r){
//这里要干什么
int mid = (l+r)>>1;
//更新子节点的sum: 子节点sum = v*父节点mul + 父节点标记的add值 * 子节点区间长度
tree[o<<1].v = (tree[o<<1].v * tree[o].mul + tree[o].add * (mid-l+1))%mod;
tree[o<<1|1].v = (tree[o<<1|1].v * tree[o].mul + tree[o].add * (r-(mid+1)+1))%mod;
//更新子节点的mul
tree[o<<1].mul = (tree[o<<1].mul * tree[o].mul)%mod;
tree[o<<1|1].mul = (tree[o<<1|1].mul * tree[o].mul)%mod;
//更新子节点的add
tree[o<<1].add = (tree[o<<1].add * tree[o].mul + tree[o].add)%mod; //先成后加
tree[o<<1|1].add = (tree[o<<1|1].add * tree[o].mul + tree[o].add)%mod;
//清除父节点的标记
tree[o].mul = 1;
tree[o].add = 0;
}
void build(int o,int l,int r){
//初始化所有结点的lazy标记
tree[o].mul = 1;
tree[o].add = 0;
if(l == r){ //叶节点
tree[o].v = a[l]%mod;
return;
}
int mid = (l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushup(o);
}
//区间乘
void update1(int o,int l,int r,int ql,int qr,ll k){
if(ql<=l && r<=qr){
tree[o].v = (tree[o].v * k)%mod;
tree[o].mul = (tree[o].mul * k)%mod;
tree[o].add = (tree[o].add * k)%mod;
return;
}
pushdown(o,l,r);
int mid = (l+r)>>1;
if(ql<=mid) update1(o<<1,l,mid,ql,qr,k);
if(qr>mid) update1(o<<1|1,mid+1,r,ql,qr,k);
pushup(o);
}
//区间加
void update2(int o,int l,int r,int ql,int qr,ll k){
if(ql<=l && r<=qr){
tree[o].v = (tree[o].v + (r-l+1)*k)%mod;
tree[o].add = (tree[o].add + k)%mod;
return;
}
pushdown(o,l,r);
int mid = (l+r)>>1;
if(ql<=mid) update2(o<<1,l,mid,ql,qr,k);
if(qr>mid) update2(o<<1|1,mid+1,r,ql,qr,k);
pushup(o);
}
//区间查询
ll querysum(int o,int l,int r,int ql,int qr){
if(ql<=l && r<=qr){
return tree[o].v%mod;
}
pushdown(o,l,r);
int mid = (l+r)>>1;
ll ans = 0;
if(ql <= mid) ans = (ans + querysum(o<<1,l,mid,ql,qr))%mod;
if(qr >= mid+1) ans = (ans + querysum(o<<1|1,mid+1,r,ql,qr))%mod;
return ans%mod;
}
int main(){
int n, m;
scanf("%d%d%d", &n, &m, &mod);
for(int i=1; i<=n; i++){
scanf("%lld", &a[i]);
}
build(1, 1, n);
while(m--){
int opt;
scanf("%d", &opt);
int x, y;
long long k;
if(opt==1){ //区间乘
scanf("%d%d%lld", &x, &y, &k);
update1(1, 1, n, x, y, k);
}else if(opt==2){ //区间加
scanf("%d%d%lld", &x, &y, &k);
update2(1, 1, n, x, y, k);
}else{ //查询区间和
scanf("%d%d", &x, &y);
printf("%lld\n", querysum(1, 1, n, x, y));
}
}
return 0;
}
P4145 线段树区间开根号
考虑到开根号是个很快的操作,一个区间开着开着就变成了0或者1
维护下线段树的某个区间是不是全0或者全1,是的话不管,不是的话暴力开根号即可。
//区间修改 转变成了 →单点暴力开根号↓
//每进入一个最小区间(只有一个值)开根号,更新标记,向上up更新
void change(int o,int l,int r,int ql,int qr) {
if(setv[o]) return;
if(l==r) { //每进入一个最小区间(l==r时区间SSEd@只有一个值)
sum[o]=(ll)sqrt(sumv[o]);//开根号
if(sumv[o]==1||sumv[o]==0) setv[o]=1; //新标记
return;
}
int mid = (l+r)>>1;
if(ql<=mid) change(lson,mid,ql,qr);
if(qr>mid) change(rson,mid+1,r,ql,qr);
pushup(o); //向上up更新
}
P4513 线段树维护最大子段和
这道题学习参考点:主要是"合并"
对于每个区间,维护一个左边的最大前缀,右边的最大后缀,以及区间内部的总答案
每次合并的时候,即答案选取左子区间的max,右子区间的max,或者左子区间的最大后缀,右子区间的最大前缀即可
题解1:https://www.luogu.com.cn/blog/41302/solution-p4513
题解2:https://www.luogu.com.cn/blog/user52559/solution-p4513
借个图
P2572 珂朵莉树|线段树维护复杂信息
这道题还可以用珂朵莉树做,暴力优雅。
线段树题解1:https://www.luogu.com.cn/blog/QVQ/solution-p2572
线段树的合并思想类似小白逛公园,分左、右、跨越区间三种清空;
优先级的问题类似于区间乘那题乘法优先,这里就是赋值优先
借个图
P1712 双指针 + 线段树(待补)
题目:https://www.luogu.com.cn/problem/P1712
题解:https://www.luogu.com.cn/blog/user5680/solution-p1712
然后接触到,线段树动态开点
思想学习:https://blog.csdn.net/lvmaooi/article/details/79729437
理解了,动态开点就是 动态给结点分配编号,不用先build建树了
动态开点,每个结点最多开logn个,所以空间复杂度就是O(nlogn)
*又听说,类似主席树的写法,到时候学到再补
P1908 逆序对,线段树+动态开点做法
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lson l,mid,tree[root].l
#define rson mid+1,r,tree[root].r
#define ls tree[root].l
#define rs tree[root].r
const int maxn = 5e5+10;
const int inf = 1e9+5;
//为什么要动态开点:因为这道题数字个数大1e9,不动态开点就先要离散化再建线段树
//静态开点的左孩子是root*2,右孩子是root*2+1,现在按编号分配
struct node{
int l,r,sum;
}tree[maxn*32];
int cnt = 1;
//合并更新父节点
void pushup(int root){
tree[root].sum=tree[tree[root].l].sum+tree[tree[root].r].sum;
}
void update(int &root,int l,int r,int pos){
if(!root) root = ++cnt; //动态分配"结点编号"
if(l == r){
tree[root].sum++;
return;
}
int mid = (l + r)>>1;
if(pos <= mid) update(tree[root].l,l,mid,pos); //要更新的pos点在前半段区间
else update(tree[root].r,mid+1,r,pos); //要更新的pos点在后半段区间
pushup(root);
}
ll query(int root,int l,int r,int ql,int qr){
ll ans = 0;
if(ql <= l && r <= qr){ //如果完全包含(l,r)区间
return tree[root].sum;
}
int mid = (l + r)>>1;
if(ql <= mid) ans += query(tree[root].l,l,mid,ql,qr); //要查询的区间包含了左边一侧 查左边
if(qr > mid) ans += query(tree[root].r,mid+1,r,ql,qr); //要查询的区间包含了右边一侧 查右边
return ans;
}
int main(){
int n;
cin>>n;
ll ans = 0;
int root = 1;
for(int i=1;i<=n;i++){
int x;
cin>>x;
ans += query(1,1,inf,x+1,inf); //查询当前输入下已经比x大的数右多少个,即(x+1,inf)范围内已插入数的个数
update(root,1,inf,x); //x位置上个数+1
}
cout<<ans<<endl;
return 0;
}
/*
参考链接:https://blog.csdn.net/qq_43906000/article/details/102155429
类似题目:https://blog.csdn.net/u012972031/article/details/88751811
*/
P1908 线段树 + 离散化做法
#include<bits/stdc++.h>
#define fi first
#define se second
#define INF 0x3f3f3f3f
#define ll long long
#define ld long double
#define mem(ar,num) memset(ar,num,sizeof(ar))
#define me(ar) memset(ar,0,sizeof(ar))
#define lowbit(x) (x&(-x))
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define lcm(a,b) ((a)*(b)/(__gcd((a),(b))))
#define maxn 500010
#define mod 1000000007
using namespace std;
ll n, tree[maxn << 2], arr[maxn], temp[maxn], ans;
void pushup(int root) {
tree[root] = tree[root << 1] + tree[root << 1 | 1];
}
//初始化各个值都是0
void build(int root,int l,int r){
if(l == r){
tree[root] = 0;
return;
}
int mid = (l+r)>>1;
build(root << 1,l,mid);
build(root <<1 | 1,mid+1,r);
pushup(root);
}
void update(int root,int l, int r,int pos) {
if(l == r) {
tree[root]++;
return;
}
int mid = (l + r) >> 1;
if(mid >= pos) update(root << 1, l, mid, pos);
else update(root << 1 | 1,mid + 1, r, pos);
pushup(root);
}
ll query( int root,int ql, int qr, int l, int r) {
if(ql <= l && r <= qr) {
return tree[root];
}
int mid = (r + l) >> 1;
ll ans = 0;
if(ql <= mid) ans += query(root << 1, ql, qr, l, mid);
if(qr > mid) ans += query(root << 1 | 1,ql, qr, mid + 1, r);
return ans;
}
int main() {
cin >> n;
for(int i = 1; i <= n; i++)
cin >> arr[i], temp[i] = arr[i];
//离散化开始
sort(temp + 1, temp + n + 1);
int num = unique(temp + 1, temp + n + 1) - temp - 1;
for(int i = 1; i <= n; i++)
arr[i] = lower_bound(temp + 1, temp + num + 1, arr[i]) - temp; //lower_bound - tempp 就是编号了
//离散化结束↑
build(1,1,50001); //这一步可以省略 无需先建树
for(int i = 1; i <= n; i++) {
update(1, 1, n, arr[i]);
ans += query(1, arr[i] + 1, n, 1, n);
}
cout << ans;
return 0;
}
/*
参考链接:https://blog.csdn.net/endeavor_g/article/details/88654684
*/
HPU校赛 线段树动态开点维护前缀和后缀和
敲了一遍竟然过了,有点开心啊!
https://www.cnblogs.com/fisherss/p/12104701.html
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n;
ll a[maxn];
int k[maxn];
ll min(ll a,ll b){
if(a < b) return a;
else return b;
}
struct tree{
ll sum[maxn];
void pushup(int root){
sum[root] = min(sum[root<<1],sum[root<<1|1]);
}
void build(int root,int l,int r){
if(l == r){
sum[root] = 0;
return;
}
int mid = (l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
pushup(root);
}
void update(int root,int l,int r,int pos,int v){
if(l == r){
sum[root] = v;
return;
}
int mid = (l + r) >> 1;
if(pos <= mid) update(root<<1,l,mid,pos,v);
else update(root<<1|1,mid+1,r,pos,v);
pushup(root);
}
ll query(int root,int l,int r,int ql,int qr){
if(ql == 0 && qr == 0) return 0; //必须处理 查询长度为0时的边界
if(ql <= l && r <= qr){
return sum[root];
}
int mid = (l + r) >> 1;
ll ans = 0x3f3f3f3f;
if(ql <= mid) ans = min(ans,query(root<<1,l,mid,ql,qr));
if(qr > mid) ans = min(ans,query(root<<1|1,mid+1,r,ql,qr));
return ans;
}
}tp,tn;
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>k[i];
for(int i=1;i<=n;i++) cin>>a[i];
ll sum = 0;
tp.build(1,1,n);
for(int i=1;i<=n;i++){
sum += a[i];//前缀
tp.update(1,1,n,i,sum);
}
sum = 0;
tn.build(1,1,n);
for(int i=n;i>=1;i--){
sum += a[i];//后缀
tn.update(1,1,n,i,sum);
}
ll ans = 0;
//求s1 + ... + sn
for(int i=1;i<=n;i++){
ans += sum;
ans -= tp.query(1,1,n,max(i-k[i]-1,0),max(i-1,0)); //除了i以外的前缀
ans -= tn.query(1,1,n,min(i+1,n+1),min(i+k[i]+1,n+1)); //除了i以外的后缀
}
cout<<ans;
return 0;
}
/*
5
1 2 3 4 4
-5 1 2 3 -4
16
*/
扫描线 与 线段树问题
讲解最好的博客1:https://blog.csdn.net/xianpingping/article/details/83032798
HDU1542参考题解2:https://www.cnblogs.com/liwenchi/p/7259171.html
借用一下博客1的代码,加了部分注释
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
/*
个人感觉:
相当于线段树在线开点 插入新点 维护sum边长
*/
const int MAX=200+10;
int mark[MAX<<2];//记录某个区间的下底边个数
double sum[MAX<<2];//记录某个区间的下底边总长度
double value[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树
//以横坐标作为线段(区间),对横坐标线段进行扫描
//扫描的作用是每次更新下底边总长度和下底边个数,增加新面积
struct seg{//线段
double l,r,h;
int d;
seg(){}
seg(double x1,double x2,double H,int c):l(x1),r(x2),h(H),d(c){}
bool operator<(const seg &a)const{
return h<a.h;
}
}s[MAX];
void pushup(int o,int left,int right){
if(mark[o]) sum[o]=value[right+1]-value[left];//mark[o]!=0表示包含了整个子区间,该子区间整个线段长度可以作为底边
else if(left == right)sum[o]=0;//叶子结点则底边长度为0(区间内线段长度为0)
else sum[o]=sum[o<<1]+sum[o<<1|1];
}
void update(int L,int R,int d,int o,int left,int right){
if(L<=left && right<=R){//该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新
mark[o]+=d;//更新底边相差差个数
pushup(o,left,right);//更新底边长
return;
}
int mid=left+right>>1;
if(L<=mid)update(L,R,d,o<<1,left,mid);
if(R>mid)update(L,R,d,o<<1|1,mid+1,right);
pushup(o,left,right);
}
//二分查找
int search(double key,double* x,int n){
int left=0,right=n-1;
while(left<=right){
int mid=left+right>>1;
if(x[mid] == key)return mid;
if(x[mid]>key)right=mid-1;
else left=mid+1;
}
return -1;
}
int main(){
int n,num=0;
double x1,x2,y1,y2;
while(cin>>n,n){
int k=0;
for(int i=0;i<n;++i){
cin>>x1>>y1>>x2>>y2;
value[k]=x1; //记录树结点->但是记录的离散化前的
s[k++]=seg(x1,x2,y1,1); //记录扫描线 下边位
value[k]=x2;
s[k++]=seg(x1,x2,y2,-1); //记录扫描线 上边位
}
sort(value,value+k);
sort(s,s+k);
int m=1;
for(int i=1;i<k;++i)//去重复端点
if(value[i] != value[i-1])value[m++]=value[i];
double ans=0;
//memset(mark,0,sizeof mark);
//memset(sum,0,sizeof sum);如果下面是i<k-1则要初始化,因为如果对第k-1条线段扫描时会使得mark,sum为0才不用初始化的
for(int i=0;i<k;++i){//扫描线段
int L=search(s[i].l,value,m);
int R=search(s[i].r,value,m)-1;
update(L,R,s[i].d,1,0,m-1);//扫描线段时更新底边长度和底边相差个数
// cout<<ans<<" "<<sum[1]*(s[i+1].h-s[i].h)<<endl;
ans+=sum[1]*(s[i+1].h-s[i].h);//新增加面积
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",++num,ans);
}
return 0;
}
/*
这里注意下
扫描线段时r-1:int R=search(s[i].l,value,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=value[right+1]-value[left];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=value[mid]-value[left]+value[right]-value[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如value[3]=1.2,value[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了
*/
树链剖分
学习地址1:https://www.bilibili.com/video/av4482146
学习地址2:https://www.bilibili.com/video/av24798851
详细博客1:https://www.cnblogs.com/chinhhh/p/7965433.html
用法总结1:https://blog.csdn.net/qq_41730604/article/details/101453877
树剖入门到入土的题目总结:https://www.cnblogs.com/Isaunoya/p/11619823.html
树链剖分求LCA(模板P3379)
我我我终于写出了自己的LCA!
还是喜欢vector啊
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+100;
vector<int> g[maxn];
/* 父亲, 深度, 子节点数, 重儿子, dfs序, dfs映射,链头, 链尾 */
int fa[maxn],depth[maxn],sz[maxn],son[maxn],id[maxn],rk[maxn],top[maxn],bot[maxn];
int cnt = 0;
void dfs(int x,int deep){
depth[x] = deep;
sz[x] = 1;
for(int li = 0;li<g[x].size();li++){
int i = g[x][li];
if(i == fa[x]) continue;
fa[i] = x;
dfs(i,deep+1);
sz[x] += sz[i];
if(sz[i] > sz[son[x]]) son[x] = i;
}
}
void dfs2(int x,int tp){
top[x] = tp;
id[x] = ++cnt;
rk[cnt] = x;
if(son[x]) dfs2(son[x],tp),bot[x] = bot[son[x]];
else bot[x] = x;
for(int li=0;li<g[x].size();li++){
int i = g[x][li];
if(i != fa[x] && i != son[x])
dfs2(i,i);
}
}
int lca(int u,int v){
while(top[u] != top[v]){
if(depth[top[u]] < depth[top[v]]) swap(u,v);
u = fa[top[u]];
}
if(depth[u] < depth[v]) return u;
return v;
}
int main(){
ios::sync_with_stdio(false);
int n,m,root;
cin>>n>>m>>root;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(root,1);
dfs2(root,root);
while(m--){
int u,v;
cin>>u>>v;
cout<<lca(u,v)<<endl;
}
return 0;
}
还有很多...吃不消了
待补