首都城市
在 JOI 的国度有 (N) 个小镇,从 (1) 到 (N) 编号,并由 (N-1) 条双向道路连接。第 (i) 条道路连接了 (A_i) 和 (B_i) 这两个编号的小镇。
这个国家的国王现将整个国家分为 (K) 个城市,从 (1) 到 (K) 编号,每个城市都有附属的小镇,其中编号为 (j) 的小镇属于编号为 (C_j) 的城市。每个城市至少有一个附属小镇。
国王还要选定一个首都。首都的条件是该城市的任意小镇都只能通过属于该城市的小镇到达。
但是现在可能不存在这样的选址,所以国王还需要将一些城市进行合并。对于合并城市 (x) 和 (y) ,指的是将所有属于 (y) 的小镇划归给 (x) 城。
你需要求出最少的合并次数。
对于 (100\%) 的数据,(1leq Nleq 2 imes 10^5)。
Tarjan
https://blog.csdn.net/qq_39972971/article/details/105074617
考虑对各个颜色建立满足如下性质的图(G):若颜色(i)形成的虚树内存在颜色(j),连边(i ightarrow j)。
若能够得到(G),则运行Tarjan算法,找到出度为零的所有强连通分量,取(size-1)的最小值即可。暴力连边时间复杂度(O(n^2))。
在树上倍增优化建图,可以得到具有同样性质的图。时间复杂度(O(nlog n))。
CO int N=2e5+10;
int K;
namespace graph{
vector<int> to[N*19];
int pos[N*19],low[N*19],dfn;
int stk[N*19],top,ins[N*19];
int col[N*19],idx,deg[N*19],siz[N*19];
void tarjan(int u){
pos[u]=low[u]=++dfn;
stk[++top]=u,ins[u]=1;
for(int v:to[u]){
if(!pos[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]) low[u]=min(low[u],pos[v]);
}
if(pos[u]==low[u]){
++idx;
do{
int x=stk[top];
col[x]=idx,ins[x]=0;
}while(stk[top--]!=u);
}
}
void main(int n){
for(int i=1;i<=n;++i)if(!pos[i]) tarjan(i);
for(int i=1;i<=n;++i)for(int x:to[i])
if(col[i]!=col[x]) ++deg[col[i]];
for(int i=1;i<=K;++i) ++siz[col[i]];
int ans=K;
for(int i=1;i<=idx;++i)if(!deg[i]) ans=min(ans,siz[i]-1);
printf("%d
",ans);
}
}
vector<int> to[N];
int pos[N],dfn;
int fa[N][18],dep[N];
int idx[N][18],num;
void dfs(int u){
pos[u]=++dfn;
idx[u][0]=++num; // [u-2^i+1,u]
for(int i=1;1<<i<=dep[u];++i){
fa[u][i]=fa[fa[u][i-1]][i-1]; // u-2^i
idx[u][i]=++num;
graph::to[num].push_back(idx[u][i-1]);
graph::to[num].push_back(idx[fa[u][i-1]][i-1]);
}
for(int v:to[u])if(v!=fa[u][0]){
fa[v][0]=u,dep[v]=dep[u]+1;
dfs(v);
}
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=17;i>=0;--i)
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return u;
for(int i=17;i>=0;--i)
if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
void link(int x,int u,int v){
int f=lca(u,v);
for(int i=17;i>=0;--i){
if(dep[fa[u][i]]>=dep[f]){
graph::to[x].push_back(idx[u][i]);
u=fa[u][i];
}
if(dep[fa[v][i]]>=dep[f]){
graph::to[x].push_back(idx[v][i]);
v=fa[v][i];
}
}
graph::to[x].push_back(idx[f][0]);
}
vector<int> col[N];
int main(){
int n=read<int>();read(K);
for(int i=1;i<n;++i){
int u=read<int>(),v=read<int>();
to[u].push_back(v),to[v].push_back(u);
}
dep[1]=1,num=K;
dfs(1);
for(int i=1;i<=n;++i){
int x=read<int>();
col[x].push_back(i);
graph::to[idx[i][0]].push_back(x);
}
for(int i=1;i<=K;++i){
sort(col[i].begin(),col[i].end(),[&](int a,int b)->bool{
return pos[a]<pos[b];
});
col[i].push_back(col[i][0]);
for(int j=1;j<(int)col[i].size();++j)
link(i,col[i][j-1],col[i][j]);
}
graph::main(num);
return 0;
}
点分治
https://hk-cnyali.com/2020/03/24/「JOISC-2020-Day4」首都城市-点分治/
考虑点分治,因为要选出来的肯定是个连通块,所以在各个分治中心考虑它。
在分治中心为(x)时处理把所有颜色变为(A_x)的答案。暴力扩展颜色,只要存在某个先决条件颜色,满足该颜色存在一个不在当前分治联通块内的点,那么把所有颜色变为(A_x)这种方案是不优的,直接停止。
时间复杂度(O(nlog n))。
CO int N=2e5+10;
vector<int> to[N];
int A[N];vector<int> col[N];
int vis[N],siz[N],all;
pair<int,int> root;
void find_root(int u,int fa){
siz[u]=1;
pair<int,int> ans={0,u};
for(int v:to[u])if(v!=fa and !vis[v]){
find_root(v,u);
siz[u]+=siz[v];
ans.first=max(ans.first,siz[v]);
}
ans.first=max(ans.first,all-siz[u]);
root=min(root,ans);
}
int key[N],fa[N];
void dfs_ins(int u,int fa){
key[u]=1,::fa[u]=fa; // in the subtree
for(int v:to[u])if(v!=fa and !vis[v]) dfs_ins(v,u);
}
void dfs_del(int u,int fa){
key[u]=0;
for(int v:to[u])if(v!=fa and !vis[v]) dfs_del(v,u);
}
int calc(int u){
dfs_ins(u,0);
deque<int> que={A[u]};
static int ins[N];ins[A[u]]=1;
vector<int> stk={A[u]}; // to clear ins
while(que.size()){
int x=que.front();que.pop_front();
for(int p:col[x]){
if(!key[p]){
for(int i:stk) ins[i]=0;
dfs_del(u,0);
return 1e9;
}
for(;p and key[p]!=2;p=fa[p]){
key[p]=2; // visited
if(!ins[A[p]]){
que.push_back(A[p]);
ins[A[p]]=1,stk.push_back(A[p]);
}
}
}
}
int ans=stk.size()-1;
for(int i:stk) ins[i]=0;
dfs_del(u,0);
return ans;
}
int solve(int u){
vis[u]=1;
int ans=calc(u);
int old=all;
for(int v:to[u])if(!vis[v]){
root={all=siz[v]<siz[u]?siz[v]:old-siz[u],0},find_root(v,0);
ans=min(ans,solve(root.second));
}
return ans;
}
int main(){
int n=read<int>(),K=read<int>();
for(int i=1;i<n;++i){
int u=read<int>(),v=read<int>();
to[u].push_back(v),to[v].push_back(u);
}
for(int i=1;i<=n;++i) col[read(A[i])].push_back(i);
root={all=n,0},find_root(1,0);
int ans=solve(root.second);
printf("%d
",ans);
return 0;
}