1. 程式人生 > >ZJOI 2015 幻想鄉戰略遊戲(動態點分治)

ZJOI 2015 幻想鄉戰略遊戲(動態點分治)

題意

https://loj.ac/problem/2135

思路

首先要明確一點,答案分佈是有單調性的。什麼意思呢?假設我們的答案在 \(u\) 節點,\((u,v)\) 之間有一條邊且 \(u\) 離答案所在的點更近,那麼 \(u\) 節點作為答案一定不比在 \(v\) 節點作答案劣。從鏈的角度分析在拓展到樹上會比較好理解這個性質。

那麼如果樹是一棵完全二叉樹,就可以 \(\log n\) 的回答了。從根節點向下跳,每次如果找到一個 \(\displaystyle2*sum[u]\geq tot\)\(sum[u]\)\(u\) 子樹點權之和, \(tot\) 為整張圖點權之和)的節點,就說明存在一個更優或不劣的答案,就往這個方向跳,直到不能跳為止。更新時直接暴力修改父親,沒什麼可說的。

雖然這棵樹不一定是完全二叉樹,但是這棵樹的“分治樹”高度一定是不超過 \(\log n\) 的(我個人不習慣使用“分治樹”的概念,而將點分治理解為重心管轄區域這樣的概念)。那麼就對這棵樹進行點分,存下每個數對應的每一層重心對應容器的標號,到它的距離,以及是容是斥。此題的每層重心的容器就是兩個變數 \(Sum,sum\) ,其中 \(Sum\) 表示此重心管轄區域權值乘距離總和,\(sum\) 表示權值總和。那麼對於 \(u\) 一層重心對應容器編號為 \(id\) ,距離 \(dis\) ,容斥係數為 \(s\) ,產生貢獻即為 \(s(Sum[id]+dis\cdot sum[id])\)

。那麼就能 \(O(\log n)\) 的詢問取某個點的答案是多少了。

修改仍為暴力修改,沒什麼好說的。

關鍵在於回答詢問,我們從整張圖的重心 \(u​\) 開始跳,這個重心 \(u​\) 將樹劈成了若干個連通塊(題目有條件,最多 \(20​\) 個),對於其中一個連通塊,我們設 \(v​\) 在這個連通塊內且與 \(u​\) 有邊。我們查詢選 \(v​\) 的得到的答案,如果小於等於 \(u​\) 的答案,那麼就要跳了。妙的是這裡跳的是這個連通塊的重心 \(w​\),這樣就可以保證複雜度了。所以,在點分的時候就要順便把每個層重心 \(u​\) 的下層重心 \(w​\) ,以及對應的 \(v​\)

存下來。複雜度 \(O(20 \cdot n\log ^2n)​\) ,還是挺飄的。

程式碼

#include<bits/stdc++.h>
#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)
using namespace std;
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
const int N=1e5+5;
template<const int maxn,const int maxm>struct Linked_list
{
    int head[maxn],to[maxm],nxt[maxm],cost[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,H;
ll sum[N*2],Sum[N*2];int Sc;
int lv[N],Sid[N][40];ll dis[N][40];bool sgn[N][40];
int sz[N];bool mark[N];
int n,q;

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(chk_min(Mi,res))C=u;
}
void dfs_init(int u,int f,ll D,bool s)
{
    Sid[u][++lv[u]]=Sc,dis[u][lv[u]]=D,sgn[u][lv[u]]=s;
    EOR(i,G,u)
    {
        int v=G.to[i],w=G.cost[i];
        if(v==f||mark[v])continue;
        dfs_init(v,u,D+w,s);
    }
}
void dac(int u,int f,int tot)
{
    int rt=u,Mi=1e9;
    CFS(u,0,tot,u,Mi);
    if(f)H.add(f,u,rt);
    mark[u]=1;
    Sc++,dfs_init(u,0,0,1);
    
    EOR(i,G,u)
    {
        int v=G.to[i],w=G.cost[i];
        if(mark[v])continue;
        Sc++,dfs_init(v,u,w,0);
        dac(v,u,sz[u]>sz[v]?sz[v]:tot-sz[u]);
    }
}
void update(int u,int val)
{
    FOR(i,1,lv[u])
    {
        int id=Sid[u][i];ll d=dis[u][i];
        sum[id]+=val;
        Sum[id]+=(ll)val*d;
    }
}
ll query(int u)
{
    ll res=0;
    FOR(i,1,lv[u])
    {
        int id=Sid[u][i];ll d=dis[u][i];bool s=sgn[u][i];
        if(s)res+=Sum[id]+d*sum[id];
        else res-=Sum[id]+d*sum[id];
    }
    return res;
}
ll Query(int u)
{
    ll res=query(u);
    EOR(i,H,u)
    {
        int v=H.to[i],w=H.cost[i];
        if(query(w)<=res)return Query(v);
    }
    return res;
}

int main()
{
    scanf("%d%d",&n,&q);
    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);
    }
    int C,Mi=1e9;
    CFS(1,0,n,C,Mi);
    memset(mark,0,sizeof(mark));
    memset(lv,0,sizeof(lv));
    Sc=0;
    dac(1,0,n);
    while(q--)
    {
        int u,val;
        scanf("%d%d",&u,&val);
        update(u,val);
        printf("%lld\n",Query(C));
    }
    return 0;
}