[NOIP模拟赛]虫图

30 篇文章 0 订阅
13 篇文章 0 订阅
题目描述
一个无向图被称为“虫图”,当且仅当:①它是一棵树;②存在某条树中的简单路径 p,满足树中任何一个点到 p 的最短距离不超过 1。现在给出一个 n 个结点 m 条边的无向图 G,没有重边,可能有自环。定义一次合并操作是:对于两个不同的结点 a 和 b,将它们合并成新结点 w,原图中如果存在边(x, a) 或者(x,b),那么新图中就存在边(x, w) 。问最少要用多少次合并操作,使得该无向图变为“虫图”。


输入格式

第1行:2个整数n(1≤n≤5000)和m(1≤m≤10^5),表示图的结点数和边数接下来m行,每行2个整数a 和 b,表示一条边


输出格式

第1行:1个整数,表示答案


输入样例
4 4
1 2
2 3
3 4

4 2


输出样例

2



题解

虫图的定义为一棵树,而树没有没有环,所以要将原图所有的环缩成一个点。缩点时记录操作数。

很容易知道路径p就是树的直径。对于直径上的任意一个结点x,都有一个以x为根的子树,对这棵子树进行合并操作,使得没有点的深度超过 1,合并时记录操作数。

最后将所有的树连接, 操作数为树的棵数-1次。


也可以直接算出最后虫树的总点数,再用n去减。下列代码是后者。


#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5005;
const int M=200005;

void Getin( int &shu ) {
	char c; int f=1; shu=0;
	for( c=getchar(); c<'0' || c>'9'; c=getchar() ) if( c=='-' ) f=-1;
	for( ; c>='0' && c<='9'; c=getchar() ) shu=shu*10+c-'0';
	shu*=f;
}

int fir[N], ecnt=1;
struct enode{ int e, next; } edge[M];
void Elink( int s, int e ) {
	edge[++ecnt].e=e; edge[ecnt].next=fir[s]; fir[s]=ecnt;
	edge[++ecnt].e=s; edge[ecnt].next=fir[e]; fir[e]=ecnt;
}

int n, m, s, e, cnt;
void Build_map() {
	Getin(n); Getin(m);
	for( int i=1; i<=m; i++ )
		Getin(s), Getin(e), Elink( s, e );
}

int dfn[N], low[N], dfs_clock, pcnt;
bool bri[M];
void Tarjan( int s, int last ) {
	dfn[s]=low[s]=++dfs_clock;
	for( int i=fir[s]; i; i=edge[i].next )
		if( !dfn[ edge[i].e ] ) {
			Tarjan( edge[i].e, i );
			low[s]=min( low[s], low[ edge[i].e ] );
			if( dfn[s]<low[ edge[i].e ] ) bri[i]=bri[i^1]=1;
		}
		else if( i!=(last^1) )
			low[s]=min( low[s], dfn[ edge[i].e ] );
}

bool vis[N], map[N][N];
void Build( int s, int col ) {
	low[s]=col; vis[s]=1;
	for( int i=fir[s]; i; i=edge[i].next )
		if( !vis[ edge[i].e ] ) {
			if( bri[i] ) Build( edge[i].e, ++pcnt );
			else Build( edge[i].e, col );
		}
}

int pot[N], tcnt;
struct tnode{ int e, next; } tree[M];
void Tlink( int s, int e ) {
	tree[++tcnt].e=e; tree[tcnt].next=pot[s]; pot[s]=tcnt;
}

int rot[N], rcnt;
void Build_tree() {
	for( int i=1; i<=n; i++ )
		if( !dfn[i] ) rot[++rcnt]=i, Tarjan( i, -1 );
	
	for( int i=1; i<=rcnt; i++ ) Build( rot[i], ++pcnt );
	
	for( int s=1; s<=n; s++ )
		for( int i=fir[s]; i; i=edge[i].next )
			if( low[s]!=low[ edge[i].e ] )
				if( !map[ low[s] ][ low[ edge[i].e ] ] ) {
					Tlink( low[s], low[ edge[i].e ] );
					map[ low[s] ][ low[ edge[i].e ] ]=1;
				}
}

int ans, sum, fa[N];
void DFS( int s, int f, int d, int &p ) {
	if( ans<d ) ans=d, p=s;
	for( int i=pot[s]; i; i=tree[i].next )
		if( tree[i].e!=f ) {
			DFS( tree[i].e, s, d+1, p );
			fa[ tree[i].e ]=s;
		}
}

void Ansp( int s, int f ) {
	sum++;
	for( int i=pot[s]; i; i=tree[i].next )
		if( tree[i].e!=f ) {
			if( vis[ tree[i].e ] ) Ansp( tree[i].e, s );
			else sum++;
		}
}

void Find_D() {
	memset( vis, 0, sizeof vis );
	for( int i=1; i<=rcnt; i++ ) { 
		ans=0; 
		DFS( low[ rot[i] ], -1, 1, s );
		ans=0; fa[s]=0;
		DFS( s, -1, 1, e );
		vis[ ans=e ]=1;
		while( fa[ ans ] )
			vis[ fa[ ans ] ]=1, ans=fa[ans];
		Ansp( low[ rot[i] ], -1 );
	}
	sum-=(rcnt-1);
}

int main() {
	Build_map();
	Build_tree();
	Find_D();
	printf( "%d\n", n-sum );
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值