debug

Description

经过一整夜的激战,RC的部队终于攻下了虫族的首都bugzilla。现在RC决定亲自带领一突击队在虫族庞大的地道中寻找他的宿命之敌——bug。 
Bugzilla有N个基地组成。这N个基地被N-1段双向地道连接在一起,每段地道都连接两个基地,并且保证任意两个基地之间都可以通过地道互相到达。Bug就藏在其中的某段地道中。 
开始时RC可以乘坐运输机降落在任何一个基地,每次到达一个基地时,RC都可选择呼叫运输机将他和他的部队运输到任一个基地,或者进入与这个基地相邻的一段地道进行搜索。为了防止bug跑进已经搜索过的地道,他在离开一个基地进入地道或登上运输机是一定会将这个基地炸毁,基地一旦被炸毁就不再和它相邻的地道连接。但这样一来,如果进入的地道另一端的基地已经在之前被炸毁,RC和它的部下就被永远困在地道中。RC是不会进入这段地道的。 
这也带来了一个问题,就是RC也许不能搜索所有的地道了。现在RC想知道的是他最多能搜索多少段地道。作为有幸被留在运输机上看风景而不必冒着成为虫食的危险钻入地下的人,你必须完成这个任务。

Input

第一行为一个整数N(2<=N<=100000),表示基地的个数,基地被编号为1~N的整数。 
以下N-1行,每行两个整数A,B,表示编号为A的基地和编号为B的基地间有一段地道。

Output

一个整数,表示RC最多能搜索的地道数目。

Sample Input

6
1 2
3 2
2 4
4 5
4 6

Sample Output

4


【分析】

        仔细分析后得知,题目等价于删除最少的边,使得树中剩下的点的度不超过2.此时,剩下的边数即为所求。

        所以,如果某点的度超过2,则该点关联的边必须删掉一些,使得该点度等于2,而如果某点的度本来小于等于2,则该点的边可以不用删。

        考虑只有一个邻接点不是叶子节点的点(设为X类点),直觉告诉我们,应该先优先删除X类点与其父亲节点之间的边。那是不是这样呢?

        证明如下

        设该X类节点为A,则A有以下两种情形:

        情况1

           A只有1个叶子节点。

           此时,A的度为21A与叶子节点的之前的边完全不用删除。

        情况2

           A1个以上的叶子节点。在这种情况下,假设最优解中保留了A与其父亲节点之间的边,我们发现,加入删除A与其父亲节点之间的边,而保留       一条原来被删除A与叶子之间的边,得到的仍然是一个最优解。这样就证明了在这种情况下可以优先删除A与其父亲节点之间的边。

        基于上面的分析,可以很容易得到算法:

            从叶子节点往上找到第一个非叶子节点A,A的度>2,则将A与它父亲节点之间的边删掉。

            此时,如果A的度仍大于2,则任意删掉与A连接的边,直到A的度为2。而A的父亲那一端,继续从叶子节点网上找非叶子节点,重复刚才的过程。

            最终,剩下的森林中,每个点的度都小于等于2.此时,剩下的边数即为所求。


【代码】

/*
    DFS实现的代码,由于Windows系统栈较小,改用手工栈实现 
*/ 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
#include<stack>          //用栈来模拟DFS的系统栈 
using namespace std;
stack<int>S;             
stack<int>F;
//S,F分别表示DFS的两个形参,表示当前节点和其父亲节点 
int N,M,du[100005],tot;               //du表示每个点的度数,tot表示删掉边数 
int x[200005],y[200005],next[200005],last[100005];   //链式前向星存图 
bool vis[100005];
void _in(int &x)
{
	char t;
	t=getchar();
	while(t<'0'||'9'<t) t=getchar();
	for(x=t-'0',t=getchar();'0'<=t&&t<='9';x=x*10+t-'0',t=getchar());
}
void _init()
{
	_in(N);
	for(int i=1;i<N;i++)
	{
		M++;                       //存双向边 
		_in(x[M]);_in(y[M]);
		next[M]=last[x[M]];
		last[x[M]]=M;
		M++;
		x[M]=y[M-1];y[M]=x[M-1];
		next[M]=last[x[M]];
		last[x[M]]=M;
		du[x[M]]++;
		du[y[M]]++;    
	}
}
void _solve()
{
	int ts,tf,i;
	S.push(1);           //任选一个点作为根 
	F.push(0);
	while(!S.empty())
	{
		ts=S.top();       //取出两个形参 
		tf=F.top();
		i=last[ts];           //i表示ts的上一条边 
		vis[ts]=true;         //标记为已访问 
		if(du[ts]==1&&y[i]==tf)    
		//如果ts的度为1,而且ts的边的另一个端点是tf,那么ts为叶子节点,回溯 
		{
			S.pop();
			F.pop();
			continue;
		}
		for(;i;i=next[i])     
		//如果ts不为叶子节点,枚举ts的每一个儿子,进入下一层DFS 
		    if(!vis[y[i]])
		    {
		    	S.push(y[i]);
		    	F.push(ts);
		    	goto END;    //这里用goto是因为要退出外层for循环 
		    }
		if(du[ts]>=3)    
		//走到这里表示ts的每个儿子都已访问,那么如果这个点的度数还大于2,那么删到只有两条边。 
		{
			tot+=(du[ts]-2);
			du[tf]--;        //只用减少父亲节点的度数,因为儿子节点已访问,不用考虑 
		}
		S.pop();     //返回上一层时,栈顶元素出栈 
		F.pop();  
		END:continue;
	}
	printf("%d\n",N-1-tot);    //因为是一棵树,一共有N-1条边,删去了tot条边 
}
int main()
{
    _init();
    _solve();
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值