1. 程式人生 > >POJ 1741 Tree

POJ 1741 Tree

題意

一棵 \(n\) 個節點的樹,求兩點間距離不超過 \(k\) 的點對對數。

思路

點分治模板題,點分治一般用來解決樹上的路徑問題,核心在於找出重心,算經過重心的合法路徑,然後以重心把樹劈成若干個小樹分別計算,每次能把樹至少砍一半,至多遞迴到 $\log n $ 層,而每層總結點數是 \(n\) ,所以複雜度為 \(n \log n\)

點分除了“算經過重心的合法路徑”這一步每題不一樣外,其他部分差不多,大體程式碼框架如下:

int sz[N];bool mark[N];
void CFS(int u,int f,int tot,int &C,int &Mi)//Centroid Finding Search
{
    sz[u]=1;int res=0;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f||mark[v])continue;
        CFS(v,u,tot,C,Mi);
        sz[u]+=sz[v];
        res=max(res,sz[v]);
    }
    res=max(res,tot-sz[u]);
    if(res<Mi)C=u,Mi=res;
}
void solve(int u)
{
    ...
}
void dac(int u,int tot)
{
    int Mi=1e9;
    CFS(u,0,tot,u,Mi);
    mark[u]=1;
    solve(u);
    
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(mark[v])continue;
        dac(v,sz[v]<sz[u]?sz[v]:tot-sz[u]);
    }
}

這道題,對於一個要 \(\text{solve}\) 的節點 \(u\)\(\text{dfs}\) 出所有從 \(u\) 出發的路徑的權值,排序,統計任意權值和不超過 \(k\) 的路徑對對數,用類似尺取的方法就可以數出經過 \(u\) 的路徑條數。然而事實上,當有兩條路徑同時經過了某個與 \(u\) 相鄰的節點 \(v\) ,那麼兩條路徑就有了一條公共邊 \((u,v)\) ,就不合法了,所以應把這種情況容斥掉,具體請看程式碼。

其實不容斥也可以寫,\(\text{solve}\) \(u\) 節點的時候只要將子樹一棵一棵加進去,每加一棵之前查詢一下加進去的子樹與馬上要加的子樹能配出多少條路徑,用樹狀陣列維護(用\(\text{short}\)

卡記憶體) ,需要一個查詢的 \(\text{dfs}\) 和更新的 \(\text{dfs}\) 。這裡不放這種寫法的程式碼。

程式碼

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=3e4+5;
template<const int maxn,const int maxm>struct Linked_list
{
    int head[maxn],to[maxm],cost[maxm],nxt[maxm],tot;
    Linked_list(){clear();}
    void clear(){memset(head,-1,sizeof(head));tot=0;}
    void add(int u,int v,int w){to[++tot]=v,cost[tot]=w,nxt[tot]=head[u],head[u]=tot;}
    #define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};Linked_list<N,N<<1>G;
int sz[N];bool mark[N];
int A[N],Ac;
int n,K,ans;

void CFS(int u,int f,int tot,int &C,int &Mi)
{
    sz[u]=1;int res=0;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f||mark[v])continue;
        CFS(v,u,tot,C,Mi);
        sz[u]+=sz[v];
        res=max(res,sz[v]);
    }
    res=max(res,tot-sz[u]);
    if(res<Mi)C=u,Mi=res;
}
void clct(int u,int f,int sum)
{
    A[Ac++]=sum;
    EOR(i,G,u)
    {
        int v=G.to[i],w=G.cost[i];
        if(v==f||mark[v])continue;
        clct(v,u,sum+w);
    }
}
int solve(int u,int l)
{
    Ac=0;clct(u,0,l);
    sort(A,A+Ac);
    int res=0,i=0,j=Ac-1;
    while(i<j)
    {
        while(i<j&&A[i]+A[j]>K)j--;
        res+=j-i;
        i++;
    }
    return res;
}
void dac(int rt,int tot)
{
    int u,Mi=1e9;
    CFS(rt,0,tot,u,Mi);
    mark[u]=1;
    ans+=solve(u,0);
    EOR(i,G,u)
    {
        int v=G.to[i],w=G.cost[i];
        if(mark[v])continue;
        ans-=solve(v,w);
        dac(v,min(sz[v],sz[rt]-sz[u]));
    }
}

int main()
{
    while(scanf("%d%d",&n,&K),n||K)
    {
        G.clear();
        memset(mark,0,sizeof(mark));
        FOR(i,1,n-1)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            G.add(u,v,w);
            G.add(v,u,w);
        }
        ans=0;
        dac(1,n);
        printf("%d\n",ans);
    }
    return 0;
}