点分治

点分治是统计树的路径问题/有序点对计数问题……这些东西的一种算法。
介绍一下它的思路。
有一个题:
求树上距离为 k 的点对个数,其中 (u,v) (v,u) 视作同一个点对。
做法:
转成长度为 k 的路径条数。
暴力枚举每一个点,统计过这个点的合法路径个数,然后把这个点标记好,之后不再将存在这个点的任何合法路径计入答案。
这样做显然是对的,因为统计路径的时候只会把每条路径算一次,算一次之后就把上面的一个点标记好,然后不进行答案的统计。
那么怎么保证复杂度呢?
先尝试下暴力的方法。
现在有一个以1为根的子树,随便找一个点 x ,把 x 标记好。
标记好之后,把 x 看作根, dfs 所有x的子树。
维护一个数组 g[N] g[v] 表示当前深度为 v 的节点个数。


在遍历到点 v 的时候,ans += g[k - dep[v]],g[dep[v]] ++;
但是这样会统计同一个子树里的答案,比如 u v x 的一个儿子 c 的子树中,它们的深度之和刚好是 k ,这样做就不对了。
那我们就先统计一遍子树 v 的答案,之后再 dfs 一遍子树 v ,把子树 v 里面的所有节点都加入到 g 数组中。


做完之后就能知道强制过 x 的路径条数了。
解决这个之后,我们对于 x 的每个儿子 v ,还需要再计算强制过 v 而不过 x 的路径条数,这个也很好办,和刚刚的做法一样,只不过不要到标记过的点就行了。
计算一下复杂度:
第一次:统计 x 的所有答案, O(n) .
第二次:统计 x 的儿子v的所有答案, [vO(sizev)]=O(n) .

也就是说,调用深度达到 k 次,时间复杂度就是 O(nk) .
每次选重心,然后标记重心,再递归到重心的子树,再次选其子树的重心。
这样做递归层数显然是 logn 的。

刚刚的题目来自于北京冬令营的水题。
可以参考下代码。

#include 
#define Rep(i,n) for(int i = 1;i <= n;++ i)
#define v edge[i].to
#define RepG(i,x) for(int i = head[x];~ i;i = edge[i].next)
using namespace std;
const int N = 100005;
struct Edge{int next,to;}edge[N << 1];
int head[N],cnt,sz[N],rt,now[N],g[N],tot,n,K;
bool done[N];
long long ans;
void save(int a,int b){edge[cnt] = (Edge){head[a],b},head[a] = cnt ++;}
void get(int x,int fa)
{
    sz[x] = 1;
    RepG(i,x)if(v != fa && !done[v])
    {
        get(v,x);
        sz[x] += sz[v];
        now[x] = max(now[x],sz[v]);
    }
    now[x] = max(now[x],tot - now[x]);
    if(now[x] < now[rt])rt = x;
}
void cal(int x,int fa,int dep)
{
    if(dep <= K)ans += g[K - dep];else return ;
    RepG(i,x)if(v != fa && !done[v])cal(v,x,dep + 1);
}
void dfs(int x,int fa,int dep)
{
    if(dep <= K)++ g[dep];
    else return;
    RepG(i,x)if(v != fa && !done[v])dfs(v,x,dep + 1);
}
void solve(int x,int s)
{
    tot = s;get(x,0);x = rt;
    g[0] = 1;done[x] = 1;
    RepG(i,x)if(!done[v])cal(v,x,1),dfs(v,x,1);
    get(x,0);Rep(i,K)g[i] = 0;
    RepG(i,x)if(!done[v])rt = 0,solve(v,sz[v]);
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&K);
    now[rt = 0] = n + 1;
    Rep(i,n - 1){int x,y;scanf("%d%d",&x,&y);save(x,y),save(y,x);}
    solve(1,n);
    printf("%I64d\n",ans);
    return 0;
}

我觉得我需要再写一些别的点分治的题……
有个东西要用到点分树,感觉这个很有用,等到自己学会就来继续写总结。
快写完了QAQ
先睡觉

#include 
#define v edge[i].to
#define Rep(i,n) for(int i = 1;i <= n;++ i)
#define RepG(i,x) for(int i = head[x];~ i;i = edge[i].next)
using namespace std;
const int N = 100005;

int node_cnt,rt,tb[N],sz[N],head[N],cnt,black,c[N],tim,tid[N],rmq[N * 2][18],lg[N * 2],fa[N],dep[N],n,m;
bool done[N];
struct Edge{int next,to;}edge[N << 1];
void save(int a,int b){edge[cnt] = (Edge){head[a],b},head[a] = cnt ++;}
struct Heap
{
    priority_queue<int>h,d;
    void ins(int val){ h.push(val); }
    void del(int val){ d.push(val); }
    void fi() { while(!d.empty() && h.top() == d.top()) h.pop(), d.pop(); }
    void pop(){ fi(); h.pop(); }
    int fir(){ fi(); return h.top();}
    int sec(){ fi(); int x,y; x = fir(); pop(); y = fir(); ins(x);return y;}
    int total(){ return h.size() - d.size();}
    void change(int x,int ty) { if(ty)ins(x); else del(x); }
}h1[N],h2[N],ans;
void add_ans(Heap &s){if(s.total() >= 2)ans.ins(s.fir() + s.sec());}
void del_ans(Heap &s){if(s.total() >= 2)ans.del(s.fir() + s.sec());}
void get(int x,int f)
{
    sz[x] = 1;tb[x] = 0;
    RepG(i,x)
    {
        if(done[v] || v == f)continue;
        get(v,x);
        sz[x] += sz[v];
        tb[x] = max(tb[x],sz[v]);
    }
    tb[x] = max(node_cnt - sz[x],tb[x]);
    if(tb[x] < tb[rt])rt = x;
}
void dfs(int x,int f,int dep,Heap &s){s.ins(dep);RepG(i,x)if(!done[v] && v != f)dfs(v,x,dep + 1,s);}
void solve(int x,int s,int org = 0)
{
    tb[0] = 1000000;node_cnt = s;
    //dfs(x,0,1,h1[x]);
    rt = 0;get(x,0);
    dfs(x,0,1,h1[rt]);
    x = rt;done[x] = 1;
    h2[x].ins(0);
    //printf("PUSH %d %d\n",x,0);
    //dfs(x,0,1,h1[x]);
    if(org)
    {
        fa[x] = org;
        h2[org].ins(h1[x].fir());
    //  printf("PUSH %d %d\n",org,h1[x].fir());
    }
    get(x,0);
    RepG(i,x)if(!done[v])solve(v,sz[v],x);
    add_ans(h2[x]);
}
void rmq_dfs(int x,int fa)
{
    rmq[++ tim][0] = dep[x] = (dep[fa] + 1);tid[x] = tim;  // tid表示x的第一次出现位置 
    RepG(i,x)if(v != fa)rmq_dfs(v,x),rmq[++ tim][0] = dep[x];
}
void rmq_build(int s)
{
    for(int i = 2;i <= s;++ i)lg[i] = lg[i >> 1] + 1;
    for(int j = 1;j <= lg[s];++ j)
        for(int i = 1;i <= (s + 1 - (1 << j) );++ i)
            rmq[i][j] = min(rmq[i][j - 1],rmq[i + (1 << (j - 1))][j - 1]);
}
int rmq_dep(int a,int b) // return dep[lca(a,b)];
{
    a = tid[a],b = tid[b];
    if(a > b)swap(a,b);
    int s = lg[b - a + 1];
    return min(rmq[a][s],rmq[b - (1 << s) + 1][s]);
}
int dis(int a,int b){return dep[a] + dep[b] - 2 * rmq_dep(a,b);}
void Modify(int x,int col)
{
    del_ans(h2[x]); h2[x].change(0,col); add_ans(h2[x]);
    int y = x;
    while(fa[x])
    {
        del_ans(h2[fa[x]]);
        if(h1[x].total())h2[fa[x]].del(h1[x].fir());
        h1[x].change(dis(fa[x],y),col);
        if(h1[x].total())h2[fa[x]].ins(h1[x].fir());
        add_ans(h2[fa[x]]);
        x = fa[x];
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("1095.out","w",stdout);
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    Rep(i,n - 1){int a,b;scanf("%d%d",&a,&b);save(a,b),save(b,a);}
    solve(1,n);rmq_dfs(1,0);rmq_build(tim);
    //Rep(i,n)printf("%d %d\n",i,fa[i]);
    scanf("%d",&m);
    black = n;
    while(m --)
    {
        char op[5];
        scanf("%s",op);
        if(op[0] == 'C')
        {
            int x;scanf("%d",&x);
            Modify(x,c[x]);
            c[x] ? ++ black : -- black;
            c[x] ^= 1;
        }
        else 
        {
            if(black < 2)printf("%d\n",black - 1);
            else printf("%d\n",ans.fir());
        }
    }
    return 0;
}

你可能感兴趣的:(数据结构)