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;
}
这道题的实现比较复杂,拼接了很多部分,实现的时候要注意想清楚细节再开始写,力争一边写对,否则调试非常麻烦。在这题中,尤其要区分缩点后的图和原图、缩点后的点数和原点数,这些细节都应该在打草纸上体现出来。
还有一个非常重要的细节是,各个部分的顺序要清晰明了,不要缩点完又返回去统计原图信息之类的,这些框架顺序也应该提前考虑清楚。