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
题目大意 : 输入一个有向图, n 个顶点, m 条边, 并赋予每个顶点一个权值,表示给这个人打电话要 X 块钱, 再输入m条边, 表示 U 可以打电话给 V,问你最少要通知多少人和最少要花多少钱可以把所有人都通知到 (别人打电话不算你的费用哦)
如图所示 :
假如这是一张已经缩点后的图, tarjan的题不是特别难的话一般都和入度出度有关,所以先看看入度出度方面有什么助于解题的地方。观察后可以发现,被通知也就是表明这个人有入度,那么想要所有人都被通知,是不是就得让入度为0的人由你来通知呢?所以,答案已经出来了,我们只要找到所有入度为0的点,(该图是1 和 4), 然后找到这个缩点后点集里所有点的权值最小的那个,再相加就OK了!
AC代码 :
#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
const int maxn = 2e3 + 5;
struct node
{
int v, next;
}e[maxn];
int dfn[maxn], low[maxn], suo[maxn], tot, cnt, scnt;
int head[maxn], val[maxn], in[maxn], n, m, index;
bool vis[maxn];
stack <int> st;
void init() {
memset(e, 0, sizeof(e));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(suo, 0, sizeof(suo));
memset(val, 0, sizeof(val));
memset(head, -1, sizeof(head));
memset(in, 0, sizeof(in));
tot = cnt = scnt = 0;
}
void add (int from, int to) {
e[++cnt].v = to;
e[cnt].next = head[from];
head[from] = cnt;
}
void tarjan (int x) {
dfn[x] = low[x] = ++tot;
vis[x] = 1;
st.push(x);
for (int i = head[x]; i != -1; i = e[i].next) {
if (!dfn[e[i].v]) {
tarjan (e[i].v);
low[x] = min (low[x], low[e[i].v]);
}
else if (vis[e[i].v]) low[x] = min (low[x], dfn[e[i].v]);
}
if (low[x] == dfn[x]) {
scnt++;
int k;
do {
k = st.top();
st.pop();
vis[k] = 0;
suo[k] = scnt;
}
while (k != x);
}
}
int main()
{
while (scanf("%d%d", &n, &m) != EOF) {
init();
for (int i = 1; i <= n ;i++) cin >> val[i];
for (int i = 0; i < m; i++) {
int ui, vi;
scanf("%d%d", &ui, &vi);
add (ui, vi);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan (i);
}
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j != -1; j = e[j].next) {
int u = suo[i];
int v =suo[e[j].v];
if (u != v) in[v]++;
}
}
int min_ = 0, sum = 0;
for (int i = 1; i <= scnt; i++) {
if (!in[i]) {
sum++; // 表示要通知的人数
int ans = 1e9;
for (int j = 1; j <= n; j++) {
if (suo[j] == i) ans = min (ans, val[j]); //找到最小的
}
min_ += ans;
}
}
cout << sum << " " << min_ << endl;
}
return 0;
}