03 并查集(带权,分类) 树状数组 线段树

POJ2236

并查集板子题,记录是否被修,然后每次修了以后更新相连的电脑。

//#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const ll max_n=2e5+100;
const ll inf=0x3f3f3f3f;
int n,xs[max_n],ys[max_n],par[max_n],d;
int fixes[max_n];
void init(int n){
    for(int i=0;i<=n;i++){
        par[i]=i;
    }
}
int find(int x){
    if(x==par[x])
        return x;
    else return par[x]=find(par[x]);
}
void unite(int x,int y){
    x=find(x);
    y=find(y);
    if(x!=y) par[x]=y;
}
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>d;d=d*d;
    for(int i=1;i<=n;i++) cin>>xs[i]>>ys[i];
    char c;
    init(n+100);
    while(cin>>c){
        if(c=='O'){
            int fix;
            cin>>fix;
            if(fixes[fix]) continue;
            else{
                fixes[fix]=1;
                for(int i=1;i<=n;i++){
                    double di;
                    di=(xs[i]-xs[fix])*(xs[i]-xs[fix])+(ys[i]-ys[fix])*(ys[i]-ys[fix]);
                    if(di<=d&&fixes[i]){
                        unite(fix,i);
                    }
                }
            }
        }else{
            int a,b;
            cin>>a>>b;
            if(find(a)==find(b)) cout<<"SUCCESS"<<endl;
            else cout<<"FAIL"<<endl;
        }
    }
    return 0;
}

银河英雄传说

https://www.luogu.com.cn/problem/P1196
带权并查集,看的我好苦啊。。
多维护俩个新的数组\(dis[i]\)\(s[i]\),分别表示\(i\)到其根节点的距离和其所在集合的元素数目。
初始化时\(dis\)数组全为0,\(s\)数组全为1;在原本的并查集\(find\)函数里面加上\(dis\)数组的更新,也就是每次加上他根节点的距离:

int find(int x){
    if(x!=par[x]){
        int k=par[x];
        par[x]=find(k);
        dis[x]+=dis[k];
        s[x]=s[par[x]];
    }return par[x];
}

这样的话首先保证集合是“稳定”的,因为当\(k\)就是集合的根节点时,\(dis[k]=0\),就不会多一次查找而改变(我一开始就卡在这了),且在未稳定下来前会不断更新。
然后在合并时,假设是\(x\)\(y\),要把\(x\)接在\(y\)尾部,所以先给\(root_x\)加上\(s[y]\),然后再把\(x\)集合的元素个数加到\(y\)上并让这两个相等:

void unite(int x,int y){
    x=find(x);y=find(y);
    if(x!=y){
        par[x]=y;
        dis[x]=s[y];
        s[y]+=s[x];
        s[x]=s[y];
    }
}

但是这样就只更新了\(x\)所在集合的根节点,所以在每次询问前都会调用\(find\)函数,就会把这个新的距离更新到整个集合(这个顺序真的一点都不能乱),然后就是普通的模拟了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll max_n=3e4+100;
const ll inf=0x3f3f3f3f;
ll n,s[max_n],par[max_n],d,dis[max_n];
void init(int n){
    for(int i=0;i<n;i++){
        par[i]=i;
    }
    fill(s,s+n,1);
}
int find(int x){
    if(x!=par[x]){
        int k=par[x];
        par[x]=find(k);
        dis[x]+=dis[k];
        s[x]=s[par[x]];
    }return par[x];
}
void unite(int x,int y){
    x=find(x);y=find(y);
    if(x!=y){
        par[x]=y;
        dis[x]=s[y];
        s[y]+=s[x];
        s[x]=s[y];
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    init(max_n-10);
    while(n--){
        char c;
        int a,b;
        cin>>c>>a>>b;
        if(c=='M'){
            unite(a,b);
        }else{
            int ra=find(a),rb=find(b);
            if(ra==rb){
                cout<<abs(dis[a]-dis[b])-1<<endl;    
            }else cout<<-1<<endl;
        }
    }
    return 0;
}

要想富先修路(bushi)

https://www.luogu.com.cn/problem/P1111
结构体先按照排个序再循环,然后记录元素个数(同上),如果等于节点数就输出,否则-1。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll max_n=1e5+100;
const ll inf=0x3f3f3f3f;
ll n,m,s[1100],par[1100];
struct fix{
    int x,y,t;
}fixes[max_n];
bool cmp(fix a,fix b){
    return a.t<b.t;
}
void init(int n){
    for(int i=0;i<n;i++){
        par[i]=i;
    }
    fill(s,s+n,1);
}
int find(int x){
    if(x!=par[x]){
        int k=par[x];
        par[x]=find(k);
        s[x]=s[par[x]];
    }return par[x];
}
void unite(int x,int y){
    x=find(x);y=find(y);
    if(x!=y){
        par[x]=y;
        s[y]+=s[x];
        s[x]=s[y];
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    init(n+10);
    for(int i=0;i<m;i++){
        cin>>fixes[i].x>>fixes[i].y>>fixes[i].t;
    }
    sort(fixes,fixes+m,cmp);
    ll res,check=0;
    for(int i=0;i<m;i++){
        unite(fixes[i].x,fixes[i].y);
        if(s[find(fixes[i].x)]==n){
            res=fixes[i].t;
            check++;
            break;
        }
    }
    if(check) cout<<res<<endl;
    else cout<<-1<<endl;
    return 0;
}

食物链

分类并查集板子题,维护三个并查集代表该动物分别处于A,B或C。情况1需要判断是否互为食物,否则合并对应集合中元素;情况2需要判断是否为同一物种或者捕食关系相反,否则合并相邻的集合元素。还会卡时间,只能用scanf,cin加速也不行。

#include<iostream>
#include<stdio.h>
//#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll max_n=1e5+6e4;
const ll inf=0x3f3f3f3f;
int n,m,par[max_n];
void init(int n){
    for(int i=0;i<n;i++){
        par[i]=i;
    }
}
int find(int x){
    if(x!=par[x]){
        par[x]=find(par[x]);
    }return par[x];
}
void unite(int x,int y){
    x=find(x);y=find(y);
    if(x!=y){
        par[x]=y;
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    //ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    scanf("%d %d",&n,&m);
    init(3*n+100);
    ll type,x,y,ans=0;
    for(int i=0;i<m;i++){
        scanf("%lld %lld %lld",&type,&x,&y);
        if(x>n||y>n){
            ans++;
            continue;
        }
        int r1=find(x),r2=find(y),r3=find(x+n);
        int r4=find(y+n),r5=find(x+2*n),r6=find(y+2*n);
        if(type==1){
            if(r1==r4||r2==r3) ans++;
            else{
                unite(r1,r2);unite(r5,r6);unite(r3,r4);
            }
        }else{
            if(r1==r2||r2==r3) ans++;
            else{
                unite(r1,r4);unite(r3,r6);unite(r5,r2);
            }
        }
    }printf("%lld\n",ans);
    return 0;
}

树状数组2

https://www.luogu.com.cn/problem/P3368
板子题,区间修改维护一个差分

//#include<iostream>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll max_n=6e5+100;
const ll inf=0x3f3f3f3f;
ll n,m,a[max_n];
int lowbit(int x){
    return x&-x;
}
struct ftree{
    ll len;
    ll ary[max_n];
    void plus(ll x,ll pos){
        while(pos<=len){
            ary[pos]+=x;
            pos+=lowbit(pos);
        }
    }
    ll getsum(ll pos){
        ll res=0;
        while(pos){
            res+=ary[pos];
            pos-=lowbit(pos);
        }
        return res;
    }
};
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ftree tree;
    cin>>n>>m;
    ll x,y,val;
    tree.len=n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    tree.plus(a[1],1);
    for(int i=2;i<=n;i++){
        tree.plus(a[i]-a[i-1],i);
    }
    while(m--){
        cin>>val;
        if(val==1){
            cin>>x>>y>>val;
            tree.plus(val,x);
            tree.plus(-val,y+1);
        }else{
            cin>>x;
            cout<<tree.getsum(x)<<endl;
        }
    }
    return 0;
}

POJ 3468

http://poj.org/problem?id=3468
奇葩玩意,G++不过C++就过了。。
哇 一开始复制粘贴的时候忘了改变量名称,干等半小时。
要求的是区间和,也就是差分的前缀和的前缀和,做变形:

\( \sum_{i=l}^r{a\left[ i \right] =\left( r-l+1 \right) \sum_{i=1}^{l-1}{d\left[ i \right] +\left( r+1 \right) \sum_{i=l}^r{d\left[ i \right] -\sum_{i=l}^r{i\times d\left[ i \right]}}}} \)
过程就省略了,就是直接加起来然后合并同类项,这个应该可以当成规律记起来。然后就是裸的树状数组了。

#include<iostream>
//#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll max_n=1e5+1000;
const ll inf=0x3f3f3f3f;
ll n,m,a[max_n];
ll lowbit(ll x){
    return x&(-x);
}
struct ftree{
    ll len;
    ll ary[max_n];
    void plus(ll x,ll pos){
        while(pos<=len){
            ary[pos]+=x;
            pos+=lowbit(pos);
        }
    }
    ll getsum(ll pos){
        ll res=0;
        while(pos){
            res+=ary[pos];
            pos-=lowbit(pos);
        }
        return res;
    }
};
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ftree tree1,tree2;
    cin>>n>>m;
    ll x,y,val;
    char c;
    tree1.len=n;tree2.len=n;
    for(ll i=1;i<=n;i++){
        cin>>a[i];
    }
    tree1.plus(a[1],1);tree2.plus(a[1],1);
    for(ll i=2;i<=n;i++){
        tree1.plus(a[i]-a[i-1],i);
        tree2.plus(i*(a[i]-a[i-1]),i);
    }
    while(m--){
        cin>>c;
        if(c=='C'){
            cin>>x>>y>>val;
            tree1.plus(val,x);tree1.plus(-val,y+1);
            tree2.plus(val*x,x);tree2.plus(-val*(y+1),y+1);
        }else{
            cin>>x>>y;
            ll res=(y-x+1)*tree1.getsum(x-1)+(y+1)*(tree1.getsum(y)-tree1.getsum(x-1))-(tree2.getsum(y)-tree2.getsum(x-1));
            cout<<res<<endl;
        }
    }
    return 0;
}

线段树1

https://www.luogu.com.cn/problem/P3372
板子题,针对不同的目标会改变那个push_up函数的形式。min或者max可以用一个板子只要把部分函数名改一下就可以;但是像这个题里的求和就得把一些函数做一些参数和赋值方式的调整;而且感觉只要一个函数满足:

\( f(X)=f(X_1)+f(X_2);X_1,X_2\in X \)
比如整个区间乘,取模等就可以带到线段树的目标函数里面。写到了一个结构体里面感觉这样用起来更舒服

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e8;
const ll maxn=1e5+100;
const ll inf=0x3f3f3f3f;
struct stree{
    ll a[maxn],t[maxn<<2],lazy[maxn<<2];
    ll ls(ll x){ return x<<1;}
    ll rs(ll x){ return x<<1|1;}
    ll mid(ll l,ll r){ return l+((r-l)>>1);}
    //k为根节点,全局查询是k=1
    void push_up(ll k){
        t[k]=t[ls(k)]+t[rs(k)];
    }
    void push_down(ll k,ll l,ll r){
        if(lazy[k]){
            ll m=mid(l,r);
            lazy[ls(k)]+=lazy[k];
            lazy[rs(k)]+=lazy[k];
            t[ls(k)]+=(m-l+1)*lazy[k];
            t[rs(k)]+=(r-m)*lazy[k];
            lazy[k]=0;
        }
    }
    //l和r为需要建树的原数组区间
    void build(ll k,ll l,ll r){
        if(l==r){
            t[k]=a[l];
        }else{
            ll m=mid(l,r);
            build(ls(k),l,m);
            build(rs(k),m+1,r);
            push_up(k);
        }
    }
    //单点修改
    void update(ll pos,ll val,ll l,ll r,ll k){
        if(l==r){
            a[pos]+=val,t[k]+=val;
        }else{
            ll m=mid(l,r);
            if(pos<=m){
                update(pos,val,l,m,ls(k));
            }else{
                update(pos,val,m+1,r,rs(k));
            }
            push_up(k);
        }
    }
    //区间修改
    void update(ll L,ll R,ll val,ll l,ll r,ll k){
        if(L<=l&&r<=R){
            lazy[k]+=val;
            t[k]+=val*(r-l+1);
        }else{
            push_down(k,l,r);
            ll m=mid(l,r);
            if(L<=m) update(L,R,val,l,m,ls(k));
            if(R>m) update(L,R,val,m+1,r,rs(k));
            push_up(k);
        }
    }
    ll ask(ll L,ll R,ll l,ll r,ll k){
        if(L<=l&&R>=r){
            return t[k];
        }else{
            push_down(k,l,r);
            ll m=mid(l,r);
            ll res=0;//视具体要求的目标赋值
            if(L<=m){
                res+=ask(L,R,l,m,ls(k));
            }
            if(R>m){
                res+=ask(L,R,m+1,r,rs(k));
            }
            return res;
        }
    }
}tree;
ll n,m;
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>tree.a[i];
    tree.build(1,1,n);
    ll t,x,y,k;
    while(m--){
        cin>>t;
        if(t==1){
            cin>>x>>y>>k;
            tree.update(x,y,k,1,n,1);
            
        }else{
            cin>>x>>y;
            cout<<tree.ask(x,y,1,n,1)<<endl;
            //for(int i=1;i<=20;i++) cout<<tree.t[i]<<' ';cout<<endl;
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Zabreture/p/13412412.html