题目:http://acm.hdu.edu.cn/showproblem.php?pid=5934
- 根据距离关系建边
- 对于强连通分量来说,只需引爆话费最小的炸弹即可引爆整个强连通分量
- 将所有的强连通分量缩成点,我们就得到了一棵树,我们只需要引爆树根的炸弹即可
- 我们可以处理出每个点所属的强连通分量的拓扑序,或者说染色法,把属于同一个强连通分量的点标上同一个数字
- 处理完强连通分量后,我们不需要建树,我们可以用并查集来维护,更好的办法是统计每个点的入度,入读为0即为根节点
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxv = ;
int V;//总共点的个数
vector<int> g[maxv];
vector<int> rg[maxv];
vector<int> vs;
bool used[maxv];
int cmp[maxv];//保存拓扑序
ll cost[maxv];//每个拓扑序的话费
ll x[],y[],r[],c[];
////scc
void dfs(int v){
used[v] = true;
for(int i = ;i<g[v].size();i++)
if(!used[g[v][i]])
dfs(g[v][i]);
vs.push_back(v);
}
void rdfs(int v,int k){
used[v] = true;
cmp[v] = k;
cost[k] = min(cost[k],c[v]);//处理出每个强连通分量的最小话费
for(int i = ;i<rg[v].size();i++)
if(!used[rg[v][i]])
rdfs(rg[v][i],k);
}
int scc(){
memset(used,,sizeof(used));
vs.clear();
for(int v = ;v<=V;v++)//这里要注意,下标是从0开始还是从1开始
if(!used[v])
dfs(v);
memset(used,,sizeof(used));
int k = ;
for(int i = vs.size()-;i>=;i--)
if(!used[vs[i]])
rdfs(vs[i],k++);
return k;
}
//////
void addedge(int i,int j){
ll dis = (y[i]-y[j])*(y[i]-y[j])+(x[i]-x[j])*(x[i]-x[j]);
if(dis <= r[i]*r[i]){
g[i].push_back(j);
rg[j].push_back(i);
}
if(dis <= r[j]*r[j]){
g[j].push_back(i);
rg[i].push_back(j);
}
}
//////
int du[];
int main(){
int t;
cin >> t;
int cas = ;
while(t--){
int n;
cin >> n;
V = n;
memset(cost,0x3f3f3f3f,sizeof(cost));
for(int i = ;i<=n;i++){
g[i].clear();
rg[i].clear();
}
for(int i = ;i<=n;i++){
scanf("%I64d%I64d%I64d%I64d",&x[i],&y[i],&r[i],&c[i]);
for(int j = ;j<i;j++){
addedge(i,j);
}
}
int k = scc();
memset(du,,sizeof(du));
for(int i = ;i<=n;i++)
for(int j = ;j<g[i].size();j++)
if(cmp[i] != cmp[g[i][j]])//拓扑序不同,度数加1
du[cmp[g[i][j]]]++;
ll ans = ;
for(int i = ;i<k;i++)
if(du[i] == )
ans += cost[i] ;
printf("Case #%d: %I64d\n",cas++,ans);
}
return ;
}