zoukankan      html  css  js  c++  java
  • LOJ3280 首都城市

    首都城市

    在 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;
    }
    
  • 相关阅读:
    java-集合框架-泛型2-泛型限定
    进程间通信
    多进程编程基础概念
    linux deb 打包流程
    linux RPM 打包流程
    Python 第一個程序
    从注册验证码入手,我保住了30%的流失用户
    为什么Web端登录需要验证码?
    网络验证码的进化:从简单图文到无感验证
    公开课 | 金融知识图谱的应用探索
  • 原文地址:https://www.cnblogs.com/autoint/p/12686971.html
Copyright © 2011-2022 走看看