LGP10060 [SNTS 2024] 树上Voronoi图 学习笔记

LGP10060 [SNTS 2024] 树上Voronoi图 学习笔记

Luogu Link

题意简述

你有一棵 \(n\) 个结点的无根树。定义树上两点间的距离为其间简单路径的边数。

\(k\) 个关键点 \(a_1,a_2,\dots,a_k\)。我们记 \(f(u)\) 为距离 \(u\) 最近的关键点的关键点序号,即满足 \(\text{dis}(u,a_i)\) 尽量小的 \(i\)。特别地,若有多个 \(i\) 满足要求,则 \(f(u)\) 取其中最小的 \(i\)

现在已知 \(F\)。问 \(A\) 有多少种可能方案。\(n\le 3\times 10^3\)

做法解析

有一个事情是显然的:\(f(u)\) 相同的点应该连成一片(实际上 \(f(u)\) 的取值可以看作一种颜色)。

但是答案并不是所有连通块的大小乘起来就完了。因为可能会出现“\(f(u)=1\) 的点离 \(a_2\) 更近”此类情况。

我们怎么考察这个呢?有一个重要的性质:如果出现不合法状况,那么一定有不合法状况存在于颜色的交界处(否则这就与“同色点必然连成一片”矛盾)。

于是……是的,我们可以直接 \(O(n^2)\) 检查所有颜色两两间的交界处。还等什么,准备树形 \(\texttt{DP}\) 吧。

对于这道题来说,一个 \(\texttt{DP}\) 方案是这样的:先把相同颜色的点“缩到一起”(只是为了把颜色当成结点来考虑动规),然后定义 \(f(i,u)\) 为:第 \(i\) 种颜色取点 \(u\) 为关键点时,其(在缩点后的树上)自己和子树的选择方案总数。

我们每访问一个颜色 \(i\),首先去把它的子问题做完,然后我们枚举:\(i\) 的关键点 \(u\)、相邻的颜色 \(j\)\(j\) 的关键点 \(v\)。啊就是这样,做完了。

反正时间复杂度是 \(O(n^2)\) 的。这题 \(O(n^2)\) 就是对的。

代码实现

不算难也不算易。

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace omodint;
using mint=m998;
const int MaxN=3e3+5;
int N,K,X,Y,F[MaxN];bool flg;
int brd[MaxN][MaxN],vis[MaxN];
int dis[MaxN][MaxN];mint dp[MaxN][MaxN],ans;
vector<int> Tr1[MaxN],S[MaxN],Tr2[MaxN];
void addudge1(int u,int v){
    Tr1[u].push_back(v);
    Tr1[v].push_back(u);
}
void befinit(int n,int k){
    flg=0,ans=0;for(int i=1;i<=n;i++)Tr1[i].clear();
    for(int i=1;i<=k;i++)S[i].clear(),Tr2[i].clear(),vis[i]=0;
}
void dfs1(int u,int f,bool &flag){
    if(flag)return;
    if(F[u]!=F[f]){
        Tr2[F[f]].push_back(F[u]);
        Tr2[F[u]].push_back(F[f]);
        brd[F[u]][F[f]]=u,brd[F[f]][F[u]]=f;
        vis[F[u]]++;if(vis[F[u]]>1)flag=1;
    }
    S[F[u]].push_back(u);
    for(auto v : Tr1[u])if(v!=f)dfs1(v,u,flag);
}
void dfs2(int rt,int u,int f,int d){
    dis[rt][u]=d;
    for(int v : Tr1[u])if(v!=f)dfs2(rt,v,u,d+1);
}
bool check(int i,int u,int j,int v){
    if(i>j)swap(i,j),swap(u,v);
    int ib=brd[i][j],jb=brd[j][i];
    return (dis[ib][u]<=dis[ib][v])&&(dis[jb][v]<dis[jb][u]);
}
void dfs3(int i,int f){
    for(int j : Tr2[i])if(j!=f)dfs3(j,i);
    for(int u : S[i]){
        dp[i][u]=1;
        for(int j : Tr2[i]){
            if(j==f)continue;mint csum=0;
            for(int v : S[j])if(check(i,u,j,v))csum+=dp[j][v];
            dp[i][u]*=csum;
        }
    }
}
void mian(){
    readis(N,K);befinit(N,K);
    for(int i=1;i<N;i++)readis(X,Y),addudge1(X,Y);
    for(int i=1;i<=N;i++)readi(F[i]);
    dfs1(1,0,flg);if(flg){puts("0");return;}
    for(int i=1;i<=N;i++)dfs2(i,i,0,0);
    int rt=F[1];dfs3(rt,0);for(auto v : S[rt])ans+=dp[rt][v];
    writil(miti(ans));
}
int Tcn;
int main(){
    readi(Tcn);
    while(Tcn--)mian();
    return 0;
}
posted @ 2025-07-25 15:08  矞龙OrinLoong  阅读(5)  评论(0)    收藏  举报