CF51F Caterpillar题解

题目传送门

题意:定义毛毛虫为一种特殊的树,形如一条链上挂着若干个叶子。特殊地,在本题中的毛毛虫允许自环但不允许重边。给定一个无向图,每次操作可以合并两个点以及两个点的出边(两个点有相同出边则出现重边,两个点之间有边则出现自环)。求将其变为毛毛虫的最小操作次数。

容易发现,一个环要想最终放到一棵树上,必然要缩成一个点(加若干自环),否则一定出现重边。于是可以先用边双连通缩点,转化成对森林的操作。

求最小的操作数等价于求最多的剩余点。如果钦定了选哪条链,则可以将链的邻居选作叶子,并将更远的点合并到这些邻居上。但是,这样做并不优,因为我们可以将链的某个邻居与链合并起来,于是该邻居的邻居就会成为叶子,显然这样不会变的更劣(只要有邻居的邻居存在)。于是,反复操作即可得出结论:最终的选法一定是选一条链以及所有叶子。于是可进一步得出结论:该链一定为树的直径。

最后对于不同连通块,只需要用 \(cnt-1\) 次操作合并起来即可。注意需要特判(缩完点后)只有一个点的连通块。

By dzhulgakov

#define N 2222
int n,m;
VI a[N],b[N];
int mark[N],tmm[N],tick;
int nn,gid[N];
VI stack;
 
int dfs1(int v, int parent)
{
	int res = tmm[v] = tick++;
	mark[v] = 1;
	stack.pb(v);
	REP(i,SZ(a[v]))
	{
		int ver = a[v][i];
		if (ver == parent) continue;
		if (mark[ver])
			res = min(res, tmm[ver]);
		else
			res = min(res, dfs1(ver,v));
	}
	mark[v] = 2;
	if (res == tmm[v])
	{
		gid[v] = nn;
		while (stack.back() != v)
		{
			gid[stack.back()] = nn;
			stack.pop_back();
		}
		stack.pop_back();
		nn++;
	}
	return res;
}
 
int glmax,total;
 
PII dfs2(int v, int parent)
{
	mark[v]=1;
	VI q;
	int res = 0;
	int leaves = 0;
	REP(i,SZ(b[v]))
	{
		int ver = b[v][i];
		if (ver == parent) continue;
		PII x = dfs2(ver,v);
		leaves += x.second;
		q.pb(x.first-x.second);
	}
	if (q.empty())
		leaves++;
	REP(i,SZ(q))
		res = max(res, q[i]+leaves+1);
	res = max(res, leaves);
	SORT(q);
	if (SZ(q) >= 2)
	{
		glmax = max(glmax, q[SZ(q)-1]+q[SZ(q)-2]+1-(parent==-1&&SZ(b[v])==1));
	}
	glmax = max(glmax,res-leaves-(parent==-1&&SZ(b[v])==1));
	if (parent==-1&&SZ(b[v])==1) leaves++;
	return PII(res,leaves);
}
 
int main()
{
	//freopen("data.in","r",stdin);
	scanf("%d%d",&n,&m);
	REP(i,m)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		x--;y--;
		a[x].pb(y);
		a[y].pb(x);
	}
	CLEAR(mark);
	FILL(gid,-1);
	tick = 0;
	nn = 0;
	REP(i,n) if (mark[i]==0)
	{
		stack.clear();
		dfs1(i,-1);
	}
	int res = 0;
	REP(i,n) REP(j,SZ(a[i]))
		if (gid[a[i][j]] != gid[i])
			b[gid[i]].pb(gid[a[i][j]]);
	CLEAR(mark);
	bool fst = true;
	REP(i,nn) if (mark[i] == 0)
	{
		if (b[i].empty())
			res++;
		else
		{
			glmax = 0;
			int leaves = dfs2(i,-1).second;
			res += glmax+leaves;
		}
		if (fst)
			fst = false;
		else
			res--;
	}
	res = n - res;
	printf("%d\n",res);
	return 0;
}

这道题的实现比较复杂,拼接了很多部分,实现的时候要注意想清楚细节再开始写,力争一边写对,否则调试非常麻烦。在这题中,尤其要区分缩点后的图和原图、缩点后的点数和原点数,这些细节都应该在打草纸上体现出来。

还有一个非常重要的细节是,各个部分的顺序要清晰明了,不要缩点完又返回去统计原图信息之类的,这些框架顺序也应该提前考虑清楚。

posted @ 2023-04-30 16:08  曹轩鸣  阅读(21)  评论(0编辑  收藏  举报