LOJ3280 首都城市

首都城市

在 JOI 的国度有 \(N\) 个小镇,从 \(1\)\(N\) 编号,并由 \(N-1\) 条双向道路连接。第 \(i\) 条道路连接了 \(A_i\)\(B_i\) 这两个编号的小镇。

这个国家的国王现将整个国家分为 \(K\) 个城市,从 \(1\)\(K\) 编号,每个城市都有附属的小镇,其中编号为 \(j\) 的小镇属于编号为 \(C_j\) 的城市。每个城市至少有一个附属小镇。

国王还要选定一个首都。首都的条件是该城市的任意小镇都只能通过属于该城市的小镇到达。

但是现在可能不存在这样的选址,所以国王还需要将一些城市进行合并。对于合并城市 \(x\)\(y\) ,指的是将所有属于 \(y\) 的小镇划归给 \(x\) 城。

你需要求出最少的合并次数。

对于 \(100\%\) 的数据,\(1\leq N\leq 2\times 10^5\)

Tarjan

https://blog.csdn.net/qq_39972971/article/details/105074617

考虑对各个颜色建立满足如下性质的图\(G\):若颜色\(i\)形成的虚树内存在颜色\(j\),连边\(i\rightarrow j\)

若能够得到\(G\),则运行Tarjan算法,找到出度为零的所有强连通分量,取\(size-1\)的最小值即可。暴力连边时间复杂度\(O(n^2)\)

在树上倍增优化建图,可以得到具有同样性质的图。时间复杂度\(O(n\log 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\n",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(n\log 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\n",ans);
	return 0;
}

posted on 2020-04-12 19:08  autoint  阅读(172)  评论(0编辑  收藏  举报

导航