P7215 [JOISC2020] 首都]

P7215 [JOISC2020] 首都

考虑对于颜色 ci,若在此颜色集合内所有节点之间的路径上出现了其他颜色(如 cj),那我们则不得不将这两种颜色合并在一起,操作数加一。

即对于颜色 ci,若设其为首都,其答案(操作数)为所有颜色为 ci 的节点之间的路径上的颜色种类数减一(因为颜色种类数包括 ci 自身)。则 ans 为所有颜色的答案的最小值。

考虑朴素的做法。具体地,对于颜色 ci,操作数初始为 0,以此颜色集合中的一个点为根,将所有颜色为 ci 的点放入队列,从队内弹出一个点 u,若其父亲 fu 的颜色此前未标记(设其颜色为 cj),则再将所有颜色为 cj 的点放入队列。不断重复,直到没有产生新的颜色节点为止。每次复杂度为 O(n),总复杂度为 O(n2)

用点分治维护此操作,以当前的分治中心为根在其子树内进行相同操作,但是当在统计过程中出现的颜色节点在当前分治中心的子树外,则直接退出,本次答案统计作废。因为若这个颜色节点在当前子树外,则它与当前分治中心的路径上必会经过上一级分治节点(即上一级分治中心的答案也会被统计在当前答案内),相当于上一级分治节点的颜色序列必包含在当前的分治中心的颜色序列中,即当前的答案只会相同或更劣。这一步操作较为关键,它能较大的优化统计答案的效率。

可以发现,虽然每次的统计仍然是 O(n) 的,但是在点分治下递归枚举的次数降到了 O(logn)。因此总的时间复杂度为 O(nlogn)。可以通过本题。

备注:有关点分治时间复杂度的证明:

  • 分治中心为当前子树的重心
  • 根据重心的性质,每进行一次分治操作,问题规模(子树大小)就会减小一半。说明点分治的递归次数为 O(logn) 级别。

另:点分治

本题代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,k;
int u,v;
int ans=1e9+10,res;
vector < int >edge[N],col[N];
int c[N];
int siz[N],f[N];
int rt;
queue<int>q;
int tag[N],c_tag[N],vis[N];
int stc[N],tp;
int maxsiz[N];
void getrt(int t,int fa,int _siz){
	siz[t]=1,maxsiz[t]=0;;
	for(auto v : edge[t]){
		if(v==fa || vis[v])continue;
		getrt(v,t,_siz);
		siz[t]+=siz[v];
    maxsiz[t]=max(maxsiz[t],siz[v]);
	}
  maxsiz[t]=max(maxsiz[t],_siz-siz[t]);
  if(maxsiz[t]<maxsiz[rt])rt=t;
}

bool is_push(vector<int> &cl){
  for(int i=0;i<cl.size();i++){
    if(!tag[cl[i]])return 1; //若统计范围在当前子树外,避免本次统计
    q.push(cl[i]);
  }
  res++;
  return 0;
}
void dfs(int t,int fa){
  f[t]=fa;
  for(auto v:edge[t]){
    if(v==fa || vis[v])continue;
    dfs(v,t);
  }
}
void calc(int t){
  res=0;
  while(!q.empty())q.pop();
  c_tag[c[t]]=1;
  if(is_push(col[c[t]]))return ;
  dfs(t,t);
  while(!q.empty()){
    int u=q.front();
    q.pop();
    if(!c_tag[c[f[u]]]){
      c_tag[c[f[u]]]=1;
      if(is_push(col[c[f[u]]]))return ;
    }
  }
  ans=min(ans,res);
}
void sign(int t,int fa){ //确定当前分治中心的子树范围
  stc[++tp]=t,tag[t]=1;
  for(auto v:edge[t]){
    if(v==fa || vis[v])continue;
    sign(v,t);
  }
}
void solve(int t){
  vis[t]=1,sign(t,t);
  calc(t);
  while(tp){
    tag[stc[tp]]=c_tag[c[stc[tp]]]=0;
    tp--;
  }
  for(auto v:edge[t]){
    if(vis[v])continue;
    rt=0;
    getrt(v,t,siz[v]);
    solve(rt);
  }
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  cin>>n>>k;
  for(int i=1;i<n;i++){
    cin>>u>>v;
    edge[u].push_back(v);
    edge[v].push_back(u);
  }
  for(int i=1;i<=n;i++){
    cin>>c[i];
    col[c[i]].push_back(i);
  }
  rt=0;
  maxsiz[rt]=n;
  getrt(1,1,n);
  solve(rt);
  cout<<ans-1<<'\n';//ans为连通块内最少颜色种类数
  return 0;
}
posted @   ?x?  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示