点分治

入门了一下点分治,发现也并不是很难。
解决的问题一般是树上满足某种条件的点对数。
思路也很直接,首先如果考虑暴力分治求解,对于一个树根来说,满足条件的点对可以分为两类:

  1. 经过树根
  2. 不经过树根

那么考虑对其分治,每次只计算第一类经过树根的点对数,然后分治其各个子树即可。
考虑此时的复杂度,如果我们能在 O ( n ) O(n) O(n) O ( n l o g n ) O(nlogn) O(nlogn) 的时间内处理出n个点中的第一类点对个数,那么整体复杂度是 O ( n 2 ) O(n^2) O(n2) 及以上的,但如果每次以树的重心为树根进行分治,那么分治层数也就来到了 l o g n logn logn 级别,此时复杂度便可以解决一些相应问题。

来一道例题,顺便存一下求重心的板子
hdu5977
题意:一颗树,每个点有一个属性,求能经过所有属性的点的路径个数(起点终点有一个不同即为不同,同一条路径反过来也要算一次),5e4个点,总属性个数不超过10。

考虑点分治的思路,就同最基本的点分治模板求树上路径长度小于等于k的点对数思路一样,既然要找经过所有属性的路径,且属性总个数很小,那么考虑状态压缩表示经过的属性,对于一个树根分治时,将到子树上每个点的路径状态保存下来,再枚举子集求解即可,当然,也要像之前提到的那道题一样,分治时会计算到从同一颗子树的两个点组成的路径,去一下重就好。特别的,当总属性个数等于1时,由于一个点就可以满足要求,但和一般的点分治处理方法无法处理此种情况,特判一下即可。

代码

#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int> P;
const int maxn=50005;
int inf=0x3f3f3f3f;

vector<int> E[maxn];
vector<int> dis;

int siz[maxn],maxs[maxn],vis[maxn];
int rt,maxx;

long long dp[1<<10],a[maxn];
long long n,k,s;
long long res;

void dfs_siz(int x,int fa){
    siz[x]=1;
    maxs[x]=0;
    for(auto i:E[x]){
        if(vis[i] || i==fa) continue;
        dfs_siz(i,x);
        maxs[x]=max(maxs[x],siz[i]);
        siz[x]+=siz[i];
    }
}

void dfs_rt(int x,int fa,int top){
    maxs[x]=max(maxs[x],siz[top]-siz[x]);
    if(maxx>maxs[x]){
        rt=x;
        maxx=maxs[x];
    }
    for(auto i:E[x]){
        if(vis[i] || i==fa) continue;
        dfs_rt(i,x,top);
    }
}

void dfs_dis(int x,int fa,int d){
    d|=(1<<(a[x]-1));
    dis.push_back(d);
    dp[d]++;
    for(auto i:E[x]){
        if(vis[i] || i==fa) continue;
        dfs_dis(i,x,d);
    }
}

long long cal(int x,int d){
    long long ret=0;
    memset(dp,0,sizeof(dp));
    dis.clear();
    dfs_dis(x,x,d);
    for(auto i:dis){
        dp[i]--;
        ret+=dp[s];
        for(int j=i;j;j=(j-1)&i){
            ret+=dp[s^j];
        }
        dp[i]++;
    }
    return ret;
}

void dfz(int x){
    maxx=inf;
    dfs_siz(x,x);
    dfs_rt(x,x,x);
    vis[rt]=1;
    res+=cal(rt,0);
    for(auto i:E[rt]){
        if(vis[i]) continue;
        res-=cal(i,1<<(a[rt]-1));
    }
    for(auto i:E[rt]){
        if(!vis[i]) dfz(i);
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n>>k){
        res=0;
        s=(1<<k)-1;
        for(int i=1;i<=n;i++){
            vis[i]=0;
            maxs[i]=0;
            E[i].clear();
        }
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<n;i++){
            int x,y;
            cin>>x>>y;
            E[x].push_back(y);
            E[y].push_back(x);
        }
        dfz(1);
        if(k==1) cout<<n*n<<'\n';
        else cout<<res<<'\n';
    }
}

再来一道例题
hdu5314
题目大意:
给一棵树,每个节点有个权值,求路径上最大点权和最小点权差值小于等于D的点对数。

思路:
点分治,维护到根的最大点权和最小点权,然后问题就转化成了一个类似于偏序问题的问题,按最大值排序,逐个插入树状数组,新插入的点最大值一定为该路径中的最大点权,区间查询即可。

代码:

#pragma comment(linker, "/STACK:102400000,102400000")
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

typedef pair<int,int> P;
const int maxn=100005;
int inf=0x3f3f3f3f;

vector<int> E[maxn];
vector<P> dis;

int siz[maxn],maxs[maxn],vis[maxn];
int rt,maxx;

int a[maxn],aa[maxn],l[maxn],r[maxn],b[maxn*3];
long long n,k,s;
long long res;

class bit{public:
    long long node[maxn*3];
    int lb(int x){return x&(-x);}
    void update(int pos,long long val){
        if(pos>0)
            for(int i=pos;i<=b[0];i+=lb(i))
                node[i]+=val;
    }
    long long ask(int pos){
        long long sum=0;
        if(pos>0)
            for(int i=pos;i;i-=lb(i))
                sum+=node[i];
        return sum;
    }
    long long query(int l,int r){
        return ask(r)-ask(l-1);
    }
}tree;

void dfs_siz(int x,int fa){
    siz[x]=1;
    maxs[x]=0;
    for(auto i:E[x]){
        if(vis[i] || i==fa) continue;
        dfs_siz(i,x);
        maxs[x]=max(maxs[x],siz[i]);
        siz[x]+=siz[i];
    }
}

void dfs_rt(int x,int fa,int top){
    maxs[x]=max(maxs[x],siz[top]-siz[x]);
    if(maxx>maxs[x]){
        rt=x;
        maxx=maxs[x];
    }
    for(auto i:E[x]){
        if(vis[i] || i==fa) continue;
        dfs_rt(i,x,top);
    }
}

void dfs_dis(int x,int fa,P d){
    d.first=max(d.first,a[x]);
    d.second=min(d.second,a[x]);
    if(b[d.first]-b[d.second]>k) return;
    dis.push_back(d);
    for(auto i:E[x]){
        if(vis[i] || i==fa) continue;
        dfs_dis(i,x,d);
    }
}

long long cal(int x,P d){
    long long ret=0;

    dis.clear();
    dfs_dis(x,x,d);
    sort(dis.begin(),dis.end());
    for(auto i:dis){
        ret+=tree.query(l[i.first],i.first);
        tree.update(i.second,1);
    }
    for(auto i:dis) tree.update(i.second,-1);
    return ret;
}

void dfz(int x){
    maxx=inf;
    dfs_siz(x,x);
    dfs_rt(x,x,x);
    vis[rt]=1;
    res+=cal(rt,P(0,inf));
    for(auto i:E[rt]){
        if(vis[i]) continue;
        res-=cal(i,P(a[rt],a[rt]));
    }
    for(auto i:E[rt]){
        if(!vis[i]) dfz(i);
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    cin>>T;
    while(T--){
        cin>>n>>k;
        for(int i=1;i<=n;i++){
            E[i].clear();
            vis[i]=0;
        }
        for(int i=0;i<=b[0];i++) b[i]=0;
        b[0]=0;
        res=0;

        for(int i=1;i<=n;i++){
            cin>>aa[i],b[++b[0]]=aa[i];
            b[++b[0]]=aa[i]-k;
        }
        sort(b+1,b+1+b[0]);
        b[0]=unique(b+1,b+1+b[0])-b-1;
        for(int i=1;i<=n;i++){
            a[i]=lower_bound(b+1,b+1+b[0],aa[i])-b;
            l[a[i]]=lower_bound(b+1,b+1+b[0],aa[i]-k)-b;
        }

        for(int i=1;i<n;i++){
            int x,y;
            cin>>x>>y;
            E[x].push_back(y);
            E[y].push_back   (x);
        }
        dfz(1);
        cout<<res*2<<endl;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值