【图论三大集合】

【图论三大集合】

不常见

https://www.cnblogs.com/Ash-ly/p/5775934.html

最小支配集

例题

定义

对于树V
从V中取尽量少的点组成一个集合, 使得 V 中剩余的点都与取出来的点有边相连

贪心策略

(1)选择一点为树根
(2)深度优先遍历得到遍历序列
(3)所得序列的反向序列的顺序进行贪心:
	一个节点既不属于支配集也不和支配集相连->将其父节点加入支配集
	-> 支配集中点的个数加 1, 标记当前节点, 当前节点的父节点, 当前节点的父节点的父节点

代码

#include<bits/stdc++.h>
using namespace std;
const int N=15000;
int n;
//邻接表存图
int e[N],ne[N],h[N],idx;
bool has_father[N];
//求dfs序 
int newpos[N];//dfs序:长度为n 
int pre[N];//存储父节点
bool visit_[N];//dfs判重
int now=0;//记录dfs遍历几个点 
//求最小支配集(MDS)
bool s[N];//是否覆盖 
bool set_[N];//是否被取 
int ans=0;
void add(int x,int y){
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x){
	newpos[now++]=x;//记录dfs序 
	for(int i=h[x];i!=-1;i=ne[i]){
		int j=e[i];
		if(!visit_[j]){
			visit_[j]=true;
			pre[j]=x;//记录父节点 
			dfs(j);
		}
	}
}
void MDS(){
	for(int i=n-1;i>=0;i--){//逆序贪心(注意now从0-(n-1)) 
		int t=newpos[i];
		if(!s[t]){//该点没被覆盖:将该点的父节点加入支配集 
			if(!set_[pre[t]]){ 
				set_[pre[t]]=1;
				ans++;
			}
			//覆盖三个点:当前节点 当前节点父节点 当前节点父节点的父节点 
			s[t]=1; 
			s[pre[t]]=1;
			s[pre[pre[t]]]=1;
		}
	}
} 
int main(){
	scanf("%d",&n);
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;i++){
		int tmp;
		scanf("%d",&tmp);
		scanf("%lld",&k[tmp]);
		int m;
		scanf("%d",&m);
		while(m--){
			int son;
			scanf("%d",&son);
			add(tmp,son);
			has_father[son]=1;
		}
	}
	int root=1;
	while(has_father[root]) root++;
	//dfs序初始化 
	visit_[1]=true;
	pre[1]=1;
	dfs(root);
	MDS();
	printf("%d",ans);
	return 0;
}
posted @ 2025-01-08 15:31  White_ink  阅读(14)  评论(0)    收藏  举报