http://acm.hdu.edu.cn/showproblem.php?pid=5934
题意:给出一些炸弹,引爆每个炸弹有各自的花费ci,他们的坐标是爆炸半径都给出,如果该炸弹爆炸炸到某个炸弹的圆心,则另一个炸弹也会被引爆。问引爆所有炸弹的最小花费是多少。
解法:很容易发现,这个爆炸是有向的,就像样例里面有的炸弹可以互相引爆,但是有的炸弹是单向引爆别的炸弹的。这里很容易想到强联通缩点,把可以互相引爆的当作一个强联通分量,我们只需要该强联通分量里面引爆花费最小的即可。单向引爆的,则记录下来,最需引爆最顶端的炸弹即可。这些只需要在tarjan算法里面改一改即可实现。
代码如下:
#include <stdio.h>
#include <string.h>
#include <vector>
#include <stack>
#include <algorithm>
#include <iostream>
using namespace std;
const int INF = 0x7fffffff;
const int N = 1005; // 点的最大个数
stack <int> sta; // 存放遍历了的点
vector <int> edge[N]; // 记录每个点能到达哪些点
int dfn[N]; // 第几个遍历
int low[N]; // 根是多少
int key[N]; // 三种状态,在队列为2,不在队列为0,已经访问但不在栈中为1
vector <int> com[N]; // 每个com里面都是一个强联通分量,最多有N个
int incom[N]; // 记录每个点在第几个强联通分量里面
int ind, com_num; // 下标, 强联通个数
int n, m; // 点数和边数
bool vst[N], pre[N], vst_p[N];
long long x[N], y[N], r[N];
int c[N], Min[N];
void init()
{
memset(dfn, -1, sizeof(dfn));
memset(low, -1, sizeof(low));
memset(key, 0, sizeof(key));
memset(incom, -1, sizeof(incom));
memset(pre, 0, sizeof(pre));
fill(Min, Min + N, INF);
memset(vst, 0, sizeof(vst));
ind = com_num = 0;
for(int i = 0; i <= n; i++)
{
edge[i].clear();
com[i].clear();
}
while(!sta.empty())
sta.pop();
}
void tarjan(int u)
{
int v;
key[u] = 2; //放入栈的标记
low[u] = dfn[u] = ind++; //u是第ind个被遍历到
sta.push(u); //遍历到的加入栈中
for(int i = 0; i < edge[u].size(); i++)
{
v = edge[u][i];
if(dfn[v] == -1) //v这个点还未遍历到,则遍历
{
tarjan(v);
low[u] = min(low[u], low[v]); //取能遍历到的最小的根序号
}
else if(key[v] == 2) {//v这个点已经遍历到且仍存于栈中,则取他们遍历顺序的较小值
low[u] = min(low[u], dfn[v]);
}
if(incom[v] != -1)
vst[incom[v]] = 1;
}
if(low[u] == dfn[u]) //如果某个点遍历下去又回到了这个u,即为强联通,会使得该情况成立,则有
{
while(!sta.empty()) //把栈中的点取出,直至取到u
{
int j = sta.top();
sta.pop();
Min[com_num] = min(Min[com_num], c[j]);
key[j] = 1; //这个点已经遍历过且不需再遍历
com[com_num].push_back(j); //把这个点放入第com个强联通分量集合里面
incom[j] = com_num; //记录下j在第com个强连通分量集合里面
if (j == u)
break;
}
vst[com_num] = 0;
com_num++; //一个强联通分量产生
}
}
int main()
{
int T, Case = 1;
cin >> T;
while(T--) {
cin >> n;
init();
for(int i = 0; i < n; i++) {
scanf("%I64d%I64d%I64d%d", &x[i], &y[i], &r[i], &c[i]);
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(i != j && (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) <= r[i] * r[i]) {
edge[i].push_back(j);
}
}
}
for(int i = 0; i < n; i++) {
if(dfn[i] == -1) {
tarjan(i);
}
}
int sum = 0;
for(int i = 0; i < com_num; i++) {
if(!vst[i])
sum += Min[i];
}
printf("Case #%d: %d\n", Case++, sum);
}
}