HDU1827:Summer Holiday (最小权点基)

27 篇文章 0 订阅

Problem Description
To see a World in a Grain of Sand
And a Heaven in a Wild Flower,
Hold Infinity in the palm of your hand
And Eternity in an hour.
—— William Blake

听说lcy帮大家预定了新马泰7日游,Wiskey真是高兴的夜不能寐啊,他想着得快点把这消息告诉大家,虽然他手上有所有人的联系方式,但是一个一个联系过去实在太耗时间和电话费了。他知道其他人也有一些别人的联系方式,这样他可以通知其他人,再让其他人帮忙通知一下别人。你能帮Wiskey计算出至少要通知多少人,至少得花多少电话费就能让所有人都被通知到吗?

Input :多组测试数组,以EOF结束。
第一行两个整数N和M(1<=N<=1000, 1<=M<=2000),表示人数和联系对数。
接下一行有N个整数,表示Wiskey联系第i个人的电话费用。
接着有M行,每行有两个整数X,Y,表示X能联系到Y,但是不表示Y也能联系X。

Output: 输出最小联系人数和最小花费。
每个CASE输出答案一行。

Sample Input
12 16
2 2 2 2 2 2 2 2 2 2 2 2
1 3
3 2
2 1
3 4
2 4
3 5
5 4
4 6
6 4
7 4
7 12
7 8
8 7
8 9
10 9
11 10

Sample Output
3 6
强连通分量内的点是可以互相通知的,因此一个强连通分量只需通知一个点就够了,又要花费最小,就通知花费最小的那个点,然后让那个点通知其他点(即缩点)。这样花费仍然不是最小的,我们不需要通知所有强连通分量,只需要通知那些没有其他强连通分量可以通知到的强连通分量,即最高强连通分量。

最高强连通分量的定义:如果一个强连通分量内部的弧都是向外的,即对于任意一点u不属于该强连通分量,不存在点v,v属于该强连通分量,使得v是u的后代(即u->v的边)。换句话说,最高强连通分量没有外部的点指向这个强连通分量,如果把最高强连通分量缩成一个点,那么是没有其他点指向这个点的。

把所有强连通分量缩成一个点,显然最高强连通分量是可以指向其他强连通分量的。我们只要通知所有最高强连通分量,那么整个图就都可以通知了(此即最小点基:所有最高强连通分量的内部的一个点构成的点集,通过该点集可以访问整个图任意一个点)。如果我们通知的是最高强连通分量内花费最低的那个点,那么这题就完美解决了(此即最小权点基:满足最小基性质,且若每个点有点权,该点集点权之和最小)。

算法步骤:
第一步先找出所有的强连通分量。(用Tarjan算法)
第二步找出所有的最高强连通分量。(思考怎么找)
第三步从每一个最高强连通分量里找出一个点权最小的点。

Tarjan算法可以找出所有的强连通分量,并且给每个点标上该点属于哪个强连通分量。我们可以在这个过程加入一个维护:维护每个强连通分量的代表点的最小点权

void Tarjan(int s){
	vis[s]=1;
	stack[++top]=s;
	dfn[s]=low[s]=++lay;
	for(int i=head[s];i+1;i=edg[i].nxt){
	 	if(!vis[edg[i].v]) Tarjan(edg[i].v);
	 	if(vis[edg[i].v]==1) low[s]=min(low[s],low[edg[i].v]);
	}
	if(low[s]==dfn[s]){
	 	++sec;
	 	do{
	  		bel[stack[top]]=sec;
	  		vis[stack[top]]=2;
	 		cmin[sec]=min(cmin[sec],value[stack[top]]);  //维护每个强连通分量的最小点权
		 }while(stack[top--]!=s);
	}
}

Tarjan算法处理完后,我们就已经维护好了每一个强连通分量得最小点权,接下来 只需要找出最高强连通分量的个数,把它们的点权加起来 就是答案了。可以遍历一遍边集,维护一个入度,然后找出所有入度为0的强连通分量,贴出代码:

//最小权点基 
#include<iostream>
using namespace std;
#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f3f
const int maxm = 2010;
const int maxn = 1010;
struct EDG{
	int u,v,nxt;
};
int n,m;
EDG edg[maxm];
int head[maxn],vis[maxn],stack[maxn],bel[maxn];
int dfn[maxn],low[maxn];
int lay,cnt,top,sec;
int value[maxn],cmin[maxn];
int info[maxn];
void init(){
	memset(head,-1,sizeof(head));
	memset(vis,0,sizeof(vis));
	top=0;cnt=0;sec=0;lay=0;
	memset(bel,0,sizeof(bel));
	memset(value,-1,sizeof(value));
	memset(cmin,INF,sizeof(cmin));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(info,0,sizeof(info));
}
void addedg(int x,int y){
	edg[cnt].u=x;
	edg[cnt].v=y;
	edg[cnt].nxt=head[x];
	head[x]=cnt++;
} 
void Tarjan(int s){
	vis[s]=1;
	stack[++top]=s;
	dfn[s]=low[s]=++lay;
	for(int i=head[s];i+1;i=edg[i].nxt){
	 	if(!vis[edg[i].v]) Tarjan(edg[i].v);
	 	if(vis[edg[i].v]==1) low[s]=min(low[s],low[edg[i].v]);
	}
	if(low[s]==dfn[s]){
	 	++sec;
	 	do{
	  		bel[stack[top]]=sec;
	  		vis[stack[top]]=2;
	  		cmin[sec]=min(cmin[sec],value[stack[top]]);
	 	}while(stack[top--]!=s);
	}
}
int x,y,ans1,ans2;
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
	 init();
	 for(int i=1;i<=n;i++) scanf("%d",&value[i]);
	 for(int i=1;i<=m;i++){
	  	scanf("%d%d",&x,&y);
	  	addedg(x,y);
	 }
	 for(int i=1;i<=n;i++){
	  	if(!vis[i]){
	   	Tarjan(i);
	  	}
	 }
	 ans1=ans2=0;
	 for(int i=0;i<cnt;i++){
	  	if(bel[edg[i].u]!=bel[edg[i].v]) info[bel[edg[i].v]]++;
	 }
	 for(int i=1;i<=sec;i++){
	  	if(!info[i]){
	   		ans1++;
	   		ans2+=cmin[i];
	  		}
	 }
	 printf("%d %d\n",ans1,ans2);  
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值