[基本操作]点分治

众所周知

点分治是基本操作

——Destinies_Gdx

 

点分治是处理树上路径问题的很好的方法,它可以把树变成一棵平衡的二叉树来使很多看起来是 $O(n^2)$ 的操作变成 $O(nlogn)$ 的

 

poj1741 Tree

给你一棵树,求长度不超过 k 的简单路径数量

sol:

点分治

每次找一个重心,计算 depth

然后用双指针法计算答案

然后递归,减去每个儿子对父亲贡献的答案

每次双指针会计算 x 的子树内部所有答案,但其实要减去两个点都在 x 的一个子树 u 的子树里的情况

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define LL long long
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
void fre()
{
    freopen("mydata.in","r",stdin);
    freopen("mydata.out","w",stdout);
}
const int maxn = 3e5 + 10;
int n,k,root;
LL ans;
int first[maxn],to[maxn << 1],nx[maxn << 1],val[maxn << 1],cnt;
inline void add(int u,int v,int w)
{
    to[++cnt] = v;
    nx[cnt] = first[u];
    first[u] = cnt;
    val[cnt] = w;
}
inline void ins(int u,int v,int w){add(u,v,w);add(v,u,w);}
int size[maxn],f[maxn],vis[maxn],sig;
int dis[maxn],stk[maxn],top;
inline void getroot(int x,int fa)
{
    size[x] = 1;f[x] = 0;
    for(int i=first[x];i;i=nx[i])
    {
        if(to[i] == fa || vis[to[i]])continue;
        getroot(to[i],x);
        size[x] += size[to[i]];
        f[x] = max(f[x],size[to[i]]);
    }
    f[x] = max(f[x],sig - size[x]);
    if(f[x] < f[root])root = x;
}
inline void getdep(int x,int fa)
{
    stk[++top] = dis[x];
    for(int i=first[x];i;i=nx[i])
    {
        if(to[i] == fa || vis[to[i]])continue;
        dis[to[i]] = dis[x] + val[i];
        getdep(to[i],x);
    }
}
inline LL calc(int x,int len)
{
    dis[x] = len;
    top = 0;
    getdep(x,0);
    sort(stk + 1,stk + top + 1);
    LL res = 0;
    int l = 1,r = top;
    while(l <= r)
    {
        while(stk[r] + stk[l] > k && r > l)r--;
        res += r - l;
        l++;
    }
    return res;
}
inline void work(int x)
{
    vis[x] = 1;
    ans += calc(x,0);
    for(int i=first[x];i;i=nx[i])
    {
        if(vis[to[i]])continue;
        ans -= calc(to[i],val[i]);
        root = 0,sig = size[to[i]];
        getroot(to[i],x);work(root);
    }
}
int main()
{
#ifdef Ez3real
    fre();
#endif
    while(1)
    {
        n = read(),k = read();
        cnt = 0;ans = 0;
        memset(first,0,sizeof(first));
        memset(vis,0,sizeof(vis));
        if(n == 0 && k == 0)return 0;
        for(int i=2;i<=n;i++)
        {
            int u = read(),v = read(),w = read();
            ins(u,v,w);
        }
        sig = n,root = 0;f[0] = 2147483233;
        getroot(1,0);
        work(root);
        printf("%lld\n",ans);
    }
}
View Code

 

bzoj2152 聪聪可可

给一棵树,每次随机两个点,求这两个点的简单路径上边权和膜 3 余 0 的概率

sol:

就是找有多少路径膜 3 余 0

所以可以记一个数组 $cnt[0/1/2]$ 表示 x 子树里膜 3 余 $0/1/2$ 的路径数

一个子树的贡献就是 $cnt[0] \times cnt[0] + cnt[1] \times cnt[2] \times 2$

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define LL long long
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
void fre()
{
    freopen("mydata.in","r",stdin);
    freopen("mydata.out","w",stdout);
}
const int maxn = 3e5 + 10;
int n,k,root;
LL ans,cc;
int first[maxn],to[maxn << 1],nx[maxn << 1],val[maxn << 1],cnt;
inline void add(int u,int v,int w)
{
    to[++cnt] = v;
    nx[cnt] = first[u];
    first[u] = cnt;
    val[cnt] = w;
}
inline void ins(int u,int v,int w){add(u,v,w);add(v,u,w);}
int size[maxn],f[maxn],vis[maxn],sig;
int dis[maxn],cntu[maxn];
inline void getroot(int x,int fa)
{
    size[x] = 1;f[x] = 0;
    for(int i=first[x];i;i=nx[i])
    {
        if(to[i] == fa || vis[to[i]])continue;
        getroot(to[i],x);
        size[x] += size[to[i]];
        f[x] = max(f[x],size[to[i]]);
    }
    f[x] = max(f[x],sig - size[x]);
    if(f[x] < f[root])root = x;
}
inline void getdep(int x,int fa)
{
    cntu[dis[x] % 3]++;
    for(int i=first[x];i;i=nx[i])
    {
        if(vis[to[i]] || to[i] == fa)continue;
        dis[to[i]] = dis[x] + val[i];
        getdep(to[i],x);
    }
}
inline LL calc(int x,int len)
{
    dis[x] = len;
    cntu[0] = cntu[1] = cntu[2] = 0;
    getdep(x,0);
    return 1LL * (cntu[0] * cntu[0] + cntu[1] * cntu[2] * 2);
}
inline void work(int x)
{
    vis[x] = 1;
    ans += calc(x,0);
    for(int i=first[x];i;i=nx[i])
    {
        if(vis[to[i]])continue;
        ans -= calc(to[i],val[i]);
        root = 0,sig = size[to[i]];
        getroot(to[i],x);work(root);
    }
}
int main()
{
#ifdef Ez3real
    fre();
#endif
    n = read();cc = n * n;//,k = read();
    //cnt = 0;ans = 0;
    //memset(first,0,sizeof(first));
    //memset(vis,0,sizeof(vis));
    //if(n == 0 && k == 0)return 0;
    for(int i=2;i<=n;i++)
    {
        int u = read(),v = read(),w = read() % 3;
        ins(u,v,w);
    }
    sig = n,root = 0;f[0] = 2147483233;
    getroot(1,0);
    work(root);
    printf("%lld/%lld\n",ans / __gcd(ans,cc),cc / __gcd(ans,cc));
}
View Code

 

bzoj1095 捉迷藏

给一棵树,每个点黑色或者白色,q 次操作,每次改变一个点的颜色,询问两个最远黑点的距离

sol:

从 Destinies_Gdx 那里学习了一波动态点分治

大概就是先建出点分治的数据结构,然后每个点维护数据结构支持修改和查询

对于这道题我们要维护 3 个可删除的堆分别表示

1.子树内到该分治节点的最大距离

2.子树内到该分治节点的分治树上父节点的最大距离

3.每个点的最大答案

 

我们把每个子树里的最长链传到 2. 里,就保证了每个子树只传上来一条链,不会出现自己走到自己的情况

全局答案就是每个点 2. 的最大值和次大值之和,每次修改的时候暴力爬树高(树高是 $O(logn)$ 的)更新这 3 个堆即可

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
const int maxn = 100010;
int n,m;
int lg[maxn + maxn];
int root,sz,size[maxn],f[maxn],vis[maxn];
vector<int> G[maxn];
int ind[maxn],dfn,reh[maxn],mn[maxn][24];
int col[maxn],dep[maxn];

namespace sp_lca
{
    int size[maxn],fa[maxn],bl[maxn];
    inline void dfs1(int x)
    {
        size[x] = 1;
        for(int i=0;i<G[x].size();i++)
        {
            int to = G[x][i];
            if(to == fa[x])continue;
            fa[to] = x;dep[to] = dep[x] + 1;
            dfs1(to);size[x] += size[to];
        }
    }
    inline void dfs2(int x,int col)
    {
        bl[x] = col;int k = 0;
        for(int i=0;i<G[x].size();i++)
        {
            int to = G[x][i];
            if(to != fa[x] && size[to] > size[k])k = to;
        }
        if(!k)return;
        dfs2(k,col);
        for(int i=0;i<G[x].size();i++)
        {
            int to = G[x][i];
            if(to != fa[x] && to != k)dfs2(to,to);
        }
    }
}
inline int lca(int x,int y)
{
    while(sp_lca::bl[x] != sp_lca::bl[y])
    {
        if(dep[sp_lca::bl[x]] < dep[sp_lca::bl[y]])swap(x,y);
        x = sp_lca::fa[sp_lca::bl[x]];
    }return dep[x] < dep[y] ? x : y; 
}
inline int caldis(int x,int y){return dep[x] + dep[y] - 2 * dep[lca(x,y)];}
struct Del_Heap
{
    priority_queue<int> A,B;
    inline void push(int x){A.push(x);}
    inline void del(int x){B.push(x);}
    inline int top()
    {
        while(!B.empty() && A.top() == B.top())A.pop(),B.pop();
        if(A.size())return A.top();
        else return 0;
    }
    inline int size(){return A.size() - B.size();}
    inline int sctop()
    {
        if(size() < 2)return 0;
        int tp = top();del(tp);
        int ntp = top();push(tp);
        return ntp;
    }
}A[maxn],B[maxn],C;

inline void findroot(int x,int fa)
{
    size[x] = 1;f[x] = 0;
    for(int i=0;i<G[x].size();i++)
    {
        int to = G[x][i];
        if(to == fa || vis[to])continue;
        findroot(to,x);
        size[x] += size[to];
        f[x] = max(f[x],size[to]);
    }
    f[x] = max(f[x],sz - size[x]);
    if(f[x] < f[root])root = x;
    //cout<<root<<endl;
}

int par[maxn];
inline void build(int x)
{
    vis[x] = 1;
    for(int i=0;i<G[x].size();i++)
    {
        int to = G[x][i];
        if(vis[to])continue;
        sz = size[x];root = 0;findroot(to,x);
        par[root] = x;build(root);
    }
}
void turn_on(int x,int rt)
{
    if(x == rt)
    {
        if(B[x].size() == 2)C.del(B[x].top());
        B[x].del(0);
    }
    if(!par[x])return;
    int fa = par[x],ds = caldis(fa,rt);
    int tp = A[x].top();A[x].del(ds);
    if(ds == tp)
    {
        int lastans = B[fa].top() + B[fa].sctop();
        int ww = B[fa].size();B[fa].del(ds);
        if(A[x].top())B[fa].push(A[x].top());
        int nowans = B[fa].top() + B[fa].sctop();
        if(nowans < lastans)
        {
            if(ww >= 2)C.del(lastans);
            if(B[fa].size() >= 2)C.push(nowans);
        }
    }
    turn_on(fa,rt);
}

void turn_off(int x,int rt)
{
    if(x == rt)
    {
        B[x].push(0);
        if(B[x].size() == 2)C.push(B[x].top());
    }
    if(!par[x])return;
    int fa = par[x],ds = caldis(fa,rt),szb = B[fa].size();
    int tp = A[x].top();A[x].push(ds);
    if(ds > tp)
    {
        int lastans = B[fa].top() + B[fa].sctop();
        if(tp)B[fa].del(tp);B[fa].push(ds);
        int nowans = B[fa].top() + B[fa].sctop();
        if(nowans > lastans)
        {
            if(szb >= 2)C.del(lastans);
            if(B[fa].size() >= 2)C.push(nowans);
        }
    }
    turn_off(fa,rt);
}
int main()
{
    n = read();
    for(int i=2;i<=n;i++)
    {
        int u = read(),v = read();
        G[u].push_back(v);
        G[v].push_back(u);
    }
    sp_lca::dfs1(1);
    sp_lca::dfs2(1,1);
    sz = n,root = 0;f[0] = 2147483233;
    findroot(1,0);
    build(root);
    for(int i=1;i<=n;i++)A[i].push(0);
    for(int i=1;i<=n;i++){col[i] = 1;turn_off(i,i);}
    int nw = n;m = read();char opt[10];
    while(m--)
    {
        scanf("%s",opt + 1);
        if(opt[1] == 'G')
        {
            if(nw <= 1)printf("%d\n",nw - 1);
            else printf("%d\n",C.top());
        }
        else
        {
            int x = read();
            if(col[x] == 1)turn_on(x,x),nw--;
            else turn_off(x,x),nw++;
            col[x] ^= 1;
        }
    }
}
View Code

 

bzoj3924 幻想乡战略游戏

给一棵带边权的树,q 次操作,每次单点点权加,和查询树上带权重心

sol:

感觉动态点分治就是要记录它的信息和它传给它父亲的信息来去重。。。

记 $A_x$ 为子树和

$B_x$ 为子树到它的权值和

$C_x$ 为子树到它父亲的权值和

算一下就可以了

注意 LCA 要 $O(1)$

#include<bits/stdc++.h>
#define LL long long
const int maxn = 400010;
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
int m,n;
int first[maxn],to[maxn],next[maxn],val[maxn],cnt;
inline void add(int u,int v,int w)
{
    to[++cnt]=v;
    val[cnt]=w;
    next[cnt]=first[u];
    first[u]=cnt;
}
int dep[maxn],dis[maxn],fat[maxn][22],s[maxn];
int id[maxn],ip[maxn],top;
void dfs(int u,int fa)  
{  
    s[++top]=u;  
    if(!id[u])id[u]=top;  
    dep[top]=dep[ip[fa]]+1;ip[u]=top;  
    for(int i=first[u];i;i=next[i])  
    {  
        int v=to[i];  
        if(v==fa)continue;  
        dis[v]=dis[u]+val[i];  
        dfs(v,u);s[++top]=u;dep[top]=dep[ip[fa]+1];  
   } 
}  
void make()
{
    for(int i=1;i<=top;i++) fat[i][0]=i;  
    for(int j=1;j<=18;j++)  
        for(int i=1;i<=top;i++) 
            if(i+(1<<j)-1<=top)  
            {  
                int x=fat[i][j-1],y=fat[i+(1<<j-1)][j-1];  
                if(dep[fat[i][j-1]]<dep[fat[i+(1<<j-1)][j-1]]) fat[i][j]=fat[i][j-1];  
                else fat[i][j]=fat[i+(1<<j-1)][j-1];  
            }
}
inline int query(int l,int r)
{
    int len=r-l+1,k=0;  
    for(k=0;1<<k+1<=len;k++);  
    if(dep[fat[l][k]]<dep[fat[r-(1<<k)+1][k]])return fat[l][k];  
    else return fat[r-(1<<k)+1][k];
}
inline int lca(int u,int v)
{
    if(id[u]>id[v]) swap(u,v);  
    return s[query(id[u],id[v])];
}
inline int caldis(int u,int v)
{
    int LCA=lca(u,v);
    return dis[u]+dis[v]-2*dis[LCA];
}
int rt,sum,f[maxn],size[maxn],vis[maxn];
inline void GetRT(int x,int fa)
{
    size[x]=1;f[x]=0;
    for(int i=first[x];i;i=next[i])
    {
        if(to[i]==fa || vis[to[i]])continue;
        GetRT(to[i],x);size[x]+=size[to[i]];  
        f[x]=max(f[x],size[to[i]]);  
    }
    f[x]=max(f[x],sum-size[x]);  
    if(f[x]<f[rt])rt=x;  
}
int ret,dv[maxn],par[maxn];
LL ans[maxn],anss[maxn],summ[maxn];
inline void work(int x)
{
    vis[x]=1;summ[x]=ret;
    for(int i=first[x];i;i=next[i])
    {
        if(vis[to[i]])continue;
        rt=0,sum=size[to[i]];
        GetRT(to[i],0);
        par[rt]=x;work(rt);
    }
}
LL cal(int u)
{
    LL ret=ans[u];
    for(int i=u;par[i];i=par[i])
    {
        LL delt=caldis(par[i],u);
        ret+=(ans[par[i]]-anss[i]);  
        ret+=delt*(summ[par[i]]-summ[i]); 
    }
    return ret;
}
LL update(int u,int va)
{
    summ[u]+=va;  
    for(int i=u;par[i];i=par[i])  
    {  
        LL di=caldis(par[i],u);  
        summ[par[i]]+=va;  
        anss[i]+=va*di;  
        ans[par[i]]+=va*di;  
    }
}
int last=1;
LL query(int u)
{
    LL ka=cal(u);
    for(int i=first[u];i;i=next[i])
    {
        LL tmp=cal(to[i]);
        if(tmp < ka)return query(to[i]);
    }
    last=u;
    return ka;
}
int main()
{
    n = read(),m = read();
    for(int i=1;i<n;i++)
    {
        int u = read(),v = read(),w = read();
        add(u,v,w),add(v,u,w);
    }top=0,dfs(1,0);
    make();sum=f[0]=n;GetRT(1,0);  
    work(rt);  
    for(int i=1;i<=m;i++)  
    {  
        int a = read(),b = read();
        update(a,b);  
        printf("%lld\n",query(last));  
    }
}
View Code

 

bzoj4012 开店

给一棵有点权和边权的树,每次给一个 $u$ 和一个值域 $[L,R]$,求点权在 $[L,R]$ 的点到 $u$ 的距离和

sol:

对每个分治重心记一下子树内点数,子树到重心的距离和,子树到重心父亲的距离和

对于点权,我们对每个点开一个 vector 存子树内不同颜色的前三种信息前缀和,每次查询二分,暴力爬树高,对每层重心的 vector 二分找到 $[L,R]$ 区间,然后计算 $[1,R] - [1,L-1]$ 的贡献即可

因为很懒所以 $LCA$ 不是 $O(1)$ 的

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
const int maxn = 200010;
int n,q,A,yr[maxn];
int first[maxn],to[maxn << 1],nx[maxn << 1],val[maxn << 1],cnt;
inline void add(int u,int v,int w)
{
    to[++cnt] = v;
    nx[cnt] = first[u];
    first[u] = cnt;
    val[cnt] = w;
}
LL dis[maxn];
int fa[maxn];
namespace LCA
{
    int dep[maxn],bl[maxn],size[maxn];
    inline void dfs1(int x)
    {
        size[x] = 1;
        for(int i=first[x];i;i=nx[i])
        {
            if(to[i] == fa[x])continue;
            fa[to[i]] = x;dep[to[i]] = dep[x] + 1;
            dis[to[i]] = dis[x] + val[i];
            dfs1(to[i]);size[x] += size[to[i]];
        }
    }
    inline void dfs2(int x,int col)
    {
        int k = 0;
        bl[x] = col;
        for(int i=first[x];i;i=nx[i])
            if(dep[to[i]] > dep[x] && size[to[i]] > size[k])k = to[i];
        if(!k)return;
        dfs2(k,col);
        for(int i=first[x];i;i=nx[i])
            if(dep[to[i]] > dep[x] && to[i] != k)dfs2(to[i],to[i]);
    }
    inline int lca(int x,int y)
    {
        while(bl[x] != bl[y])
        {
            if(dep[bl[x]] < dep[bl[y]])swap(x,y);
            x = fa[bl[x]];
        }
        return dep[x] > dep[y] ? y : x;
    }
}
struct Node
{
    LL col,sum,sig,cnt;
    inline bool operator < (const Node &b)const{return col < b.col;}
};vector<Node> G[maxn];
inline LL caldis(int x,int y)
{
//    cout<<dis[x] + dis[y] - 2 * dis[LCA::lca(x,y)]<<endl;
    if(!x || !y)return 0;
    return dis[x] + dis[y] - 2 * dis[LCA::lca(x,y)];
}
int f[maxn],size[maxn],vis[maxn],par[maxn],sig,root;
void findroot(int x,int fa)
{
    f[x] = 0,size[x] = 1;
    for(int i=first[x];i;i=nx[i])
    {
        if(to[i] == fa || vis[to[i]])continue;
        findroot(to[i],x);size[x] += size[to[i]];
        f[x] = max(f[x],size[to[i]]);
    }f[x] = max(f[x],sig - size[x]);
    if(f[x] < f[root])root = x;
}
void add_node(int x,int fa,int rt)
{
    G[rt].push_back((Node){yr[x],caldis(x,rt),(par[rt] ? caldis(x,par[rt]) : 0),1});
    
    for(int i=first[x];i;i=nx[i])
    {
        if(to[i] == fa || vis[to[i]])continue;
        add_node(to[i],x,rt);
    }
}
void build(int x)
{
    vis[x] = 1;add_node(x,0,x);
    G[x].push_back((Node){-1,0,0,0});
    sort(G[x].begin(),G[x].end());
    for(int i=1;i<G[x].size();i++)
    {
        G[x][i].sum += G[x][i-1].sum;
        G[x][i].sig += G[x][i-1].sig;
        G[x][i].cnt += G[x][i-1].cnt;
    }
    for(int i=first[x];i;i=nx[i])
    {
        if(vis[to[i]])continue;
        root = 0;sig = size[to[i]];
        findroot(to[i],0);par[root] = x;
        build(root);
    }
}
LL query(int x,int ql,int qr)
{
    LL ans = 0;
    for(int i=x;i;i=par[i])
    {
        int st,ed;
        int l = 0,r = G[i].size() - 1;
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(G[i][mid].col <= qr)l = mid + 1;
            else r = mid - 1;
        }
        ed = l - 1;
        l = 0,r = G[i].size() - 1;
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(G[i][mid].col <= ql - 1)l = mid + 1;
            else r = mid - 1;
        }
        st = l - 1;
    //    cout<<st<<" "<<ed<<endl;
        ans += (G[i][ed].sum - G[i][st].sum);
        if(i != x) ans += (G[i][ed].cnt - G[i][st].cnt) * caldis(i,x);
        if(par[i]) ans -= (G[i][ed].sig - G[i][st].sig) + (G[i][ed].cnt - G[i][st].cnt) * caldis(x,par[i]);
    }return ans;
}
int main()
{
    n = read(),q = read(),A = read();
    for(int i=1;i<=n;i++)yr[i] = read();
    for(int i=2;i<=n;i++)
    {
        int u = read(),v = read(),w = read();
        add(u,v,w);add(v,u,w);
    }LCA::dep[1] = 1;LCA::dfs1(1);LCA::dfs2(1,1);
    sig = n;size[0] = f[0] = 2147483233;
    findroot(1,0);build(root);
    LL lastans = 0;
    while(q--)
    {
        int x = read(),a = read(),b = read();
        int l=min((a+lastans)%A,(b+lastans)%A);
        int r=max((a+lastans)%A,(b+lastans)%A);
        printf("%lld\n",lastans=query(x,l,r));
    }
}
View Code

 

posted @ 2018-11-21 19:28  探险家Mr.H  阅读(279)  评论(0编辑  收藏  举报