LINK

题意

一个 n n n个点 m m m条边的无向图

①.选择一个点价值减一

②.如果一条边的两个点都被选择价值加一

③.如果一条边的两个点只选择一个点价值减一

问你最大价值


显然拿掉环中的所有点答案不会变差

设一个环中有 x x x个点那么至少有 x x x条边,这样①和②的贡献就不会是负数

那么如果这个环连接着一些其他点呢?我们就选掉这些点

此时选择一个点,多一条边,答案仍然不会变差

所以贪心策略就出来了

跑一遍边双连通,显然环中所有点都被我们选走

如果一个点不在环中,但是和环在一个连通块,那么我们肯定也会选它

因为不选答案会变差,选答案不变(选择一个点,获得一条边)

所以只需要考虑环中点的贡献即可

把环中点标记起来选掉,如果一条边连接两个环中点那么获得贡献

这样计算权值即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e6+10;
int n,m,ans,casenum;
int dfn[maxn],low[maxn],stac[maxn],id,top,bcc[maxn],bccn,vis[maxn],siz[maxn];
vector<int>vec[maxn];
void tarjan(int u,int fa)
{
	dfn[u] = low[u] = ++id, stac[++top] = u;
	for( auto v:vec[u] )
	{
		if( !dfn[v] )
		{
			tarjan( v ,u );
			low[u] = min( low[u],low[v] );
		}
		else if( v!=fa )	low[u] = min( low[u],dfn[v] );
	}
	if( dfn[u]!=low[u] )	return;
	++bccn; int temp;
	while( temp = stac[top--] )
	{
		bcc[temp] = bccn; siz[bccn]++;
		if( temp==u )	break;
	}
}
int main()
{
	int t; cin >> t;
	while( t-- )
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++)
		{
			int l,r; scanf("%d%d",&l,&r);
			vec[l].push_back( r ), vec[r].push_back( l );
		}
		for(int i=1;i<=n;i++)
			if( !dfn[i] )	tarjan(i,i);
		for(int i=1;i<=n;i++)
		{
			if( siz[bcc[i]]>=2 )	vis[i] = 1, ans -= 2;//本来是-1,但是下面计算边贡献正反算了2次,索性这里也扩大两倍,最后除以2 
		}
		for(int i=1;i<=n;i++)
		for(auto v:vec[i] )
			if( vis[i] && vis[v] )	ans++;
		cout << "Case #" << ++casenum << ": " << ans/2 << endl;
		for(int i=1;i<=n;i++)
		{
			vec[i].clear();
			dfn[i] = low[i] = vis[i] = bcc[i] = siz[i] = 0;
		}
		top = id = bccn = ans = 0;
	}
}