HDU 3313 Key Vertex 胡搞(网络流最小割拆点什么的人家才不会呢)

problem大意:给you一个无loop有向graph,求它的key vertex数目。(最近刚过了英语六级,耶,1Y,炫耀~~~~~)

 

什么是key vertex呢?就是去掉以后从起点到不了终点了的点。跟割点还是有区别的吧?以下就简称key了。

 

一看就是“哔——”的图论,可能还跟最小割有关系,但构不出图,唉,网络流不熟悉啊。

 

但想了想么还是可以做的,起点到终点随便找条路,key必都在这条路径上(没有路么就全都算key了)。

然后再开始搜,但是搜到路径上第一个点就不从这个点往下搜,若如此做还是能搜到第一个点后面的点,那这个点就不是key了。反之其即为key。

 

具体做法:

初始化:

先把路径上的点从0开始标号,不在路径上的点都标为-1。标号对应的是哪条边也记录一下吧。

把限制limit定为1(因为我们要判断路径上第1个点是不是key,所以搜到第1个点就不要再往下搜。而0和最后个点必是key,所以limit从1开始),同时把起点出发的第一条边放入队列。

 

开始啦:

队列里拿出边,不管怎样把他的下一条边放入队列(我用正向表存的)。

这条边所到的点有三种情况:(当然搜到过的点就不要再搜了)

1、标号小于limit,说明还可以继续搜,把这个点出发的第一条边放入队列。

2、标号等于limit,啥事不干。

3、标号大于limit,那说明标号前面的都不是key了(已经找到的key当然除外),那就把limit定为当前标号(因为我们要看这个点是不是key了嘛,前面的都不用管了),同时别忘了把之前的limit这个点出发的第一条边放入队列(因为第2种情况时没放进去)。

如果标号是路径上最后个点那就break掉吧,这个点无需判断必是key了。

 

如果队列空了,说明当前的limit对应的点是key,好了,现在要判断下一个点了,那么从这个点出发的边就要加进来了,然后result++,limit++。当然limit对应了路径终点时break掉就好了。

 

最后把result加上头尾的2就是答案了。

 

因为每个边只扫描了一遍就是O(E)的复杂度啦。跑了1000多ms。

搞完看discuss说输入就要1s,那跑1s内的那些肯定用了输入外挂了,也猥琐滴用了下,轻松第一啦,唔嘻嘻嘻嘻嘻嘻~~~~~~~~

 

 

#include<iostream>
#include<queue>
#define INF 2100000000
using namespace std;
struct Edge{
	int u,v,next;
};
Edge edge[300000];
int head[100000],en;
bool mark[100000];
int N,M,S,E;
int f_min(int x,int y){
	return x<y?x:y;
}
void insert(int u,int v){
	edge[en].u=u;edge[en].v=v;
	edge[en].next=head[u];
	head[u]=en++;
}
int Scan(){  
    int res = 0 , ch ;  
    while( !( ( ch = getchar() ) >= '0' && ch <= '9' ) );
    res = ch - '0' ;  
    while( ( ch = getchar() ) >= '0' && ch <= '9' )  
        res = res * 10 + ( ch - '0' ) ;  
    return res ;
}  
bool get_in(){
	if(scanf("%d%d",&N,&M)==EOF)return 0;
	memset(head,-1,sizeof(head));en=0;
	int u,v;
	while(M--){
		u=Scan();v=Scan();
		insert(u,v);
	}
	scanf("%d%d",&S,&E);
	return 1;
}
int id[100000],path[100000],pn;
int cur[100000];
bool find_path(){
	path[0]=S;pn=1;
	int i,e,u;
	for(i=0;i<N;i++){
		cur[i]=head[i];
		mark[i]=0;
	}
	mark[S]=1;
	while(pn){
		u=path[pn-1];
		for(;cur[u]!=-1;cur[u]=edge[cur[u]].next){
			if(!mark[edge[cur[u]].v])break;
		}
		e=cur[u];
		if(e==-1){
			pn--;
			continue;
		}
		mark[edge[e].v]=1;
		path[pn++]=edge[e].v;
		if(edge[e].v==E)break;
	}
	return pn>0;
}
int que[300000],qhead,qtail;
void limit_search(){
	int lim=1;
	int v,e,res=2;
	for(int i=0;i<N;i++)mark[i]=0;
	qhead=qtail=0;
	que[qtail++]=head[S];
	mark[S]=1;
	while(lim<pn-1){
		while(qtail>qhead){
			e=que[qhead++];
			v=edge[e].v;
			if(edge[e].next!=-1)que[qtail++]=edge[e].next;
			if(mark[v])continue;
			mark[v]=1;
			if(id[v]<lim){if(head[v]!=-1)que[qtail++]=head[v];}
			else if(id[v]>lim){
				if(head[path[lim]]!=-1)que[qtail++]=head[path[lim]];
				lim=id[v];
				if(lim==pn-1)break;
			}
		}
		if(lim==pn-1)break;
		if(head[path[lim]]!=-1)que[qtail++]=head[path[lim]];
		res++;
		lim++;
	}
	printf("%d\n",res);
}
void run(){
	int i;
	for(i=0;i<N;i++)id[i]=-1;
	if(!find_path()){printf("%d\n",N);return;}
	for(i=0;i<pn;i++)id[path[i]]=i;
	limit_search();
}
int main(){
	while(get_in())run();
	return 0;
}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值