數據結構小結
好累啊這幾天沉迷數據結構
高數被我鴿幾天了,單詞又背了遍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;
}
還有很多...吃不消了
待補