这是现场赛的我们卡的这道题,两个多小时到最后也没弄出来,唉!!结束后当场问了其他学校的巨巨才知道是一道强联通题,才知道有强连通这种东东,前天打了杭电的重赛还是卡了这道题,我还是在金巨的循循善导在才学会了的。其实我的当时在现场赛写的代码我到现在也想不出错在了哪,最后贴出, 希望有大神找一下我的当时的代码错哪了!
题目就是给你一堆炸弹, 每个炸弹都给出了位置,爆炸的范围(这个数竟然可能有负的也是对出题人无语了)与及这个炸弹爆炸的花费,当一个炸弹爆炸时这个炸弹爆炸范围内的炸弹都会爆炸。首先枚举每个炸弹然后这个炸弹爆炸范围内的炸弹都建立一条有向边,因为每个炸弹的爆炸范围不同,如K炸弹的爆炸范围较大,当K爆炸时能使P爆炸,但是P爆炸范围较小,P爆炸不能一定能使K爆炸。建完有向图后用Tarjan算法进行强连通缩点(如果不会建议先去自己学习,因为死磕是行不通的。我当时也是看不懂别人的博客较为纠结,然后听从金巨的建议找来了“大白书”(算法竞赛入门经典)学习这种算法,网上相关的博客都是说得糊里糊涂的,这是金前辈的总结),缩点完成后先找出每个强连通内的最小花费,最后在根据一开始的有向图给每个强连通连上边,找出入度为零的点,这些点的花费和就是答案
下面是AC代码
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1007;
const ll inf = 0x7fffffffffffffffll;
int pre[maxn], low[maxn], sccno[maxn], num;
vector edge[maxn];
stack s;
void init(int n){ /// 初始化
memset( pre, 0, sizeof( pre));
memset( low, 0, sizeof( low));
memset( sccno, 0, sizeof( sccno));
for(int i = 1; i <= n; i ++) edge[i].clear();
num = 0;
}
void dfs(int key){ /// Tarjan算法
pre[key] = low[key] = ++ num;
s.push(key);
int tt = edge[key].size(), temp;
for(int i = 0; i < tt; i ++){
temp = edge[key][i];
if(!pre[temp]){
dfs(temp);
if(low[temp] < low[key]) low[key] = low[temp];
}
else if(!sccno[temp]){
if(pre[temp] < low[key]) low[key] = pre[temp];
}
}
if(low[key] == pre[key]){ /// 缩点
while(s.top() != key){
sccno[s.top()] = key;
s.pop();
}
sccno[key] = key;
s.pop();
}
}
ll x[maxn], y[maxn], r[maxn], c[maxn];
int vis[maxn];
bool judge(ll p, ll q, ll w){ /// 范围的判断,此处没有用开方,精度更高
return p*p + q*q <= w*w;
}
int main(){
int t, n, u = 0;
scanf("%d", &t);
while(t --){
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
scanf("%I64d %I64d %I64d %I64d", &x[i], &y[i], &r[i], &c[i]);
init(n);
for(int i = 1; i < n; i ++) /// 建有向图
for(int j = i + 1; j <= n; j ++){
if(judge(x[i]-x[j], y[i]-y[j], r[i])) edge[i].push_back(j);
if(judge(x[i]-x[j], y[i]-y[j], r[j])) edge[j].push_back(i);
}
for(int i = 1; i <= n; i ++) /// Tarjan 强连通缩点
if(!pre[i]) dfs(i);
for(int i = 1; i <= n; i ++) /// 找出每个强连通的最小花费
c[sccno[i]] = min( c[i], c[sccno[i]]);
memset( vis, 0, sizeof( vis));
for(int i = 1; i <= n; i ++){ /// 找出每个强连通的入度
int kk = edge[i].size();
for(int j = 0; j < kk; j ++){
int l = edge[i][j];
if(sccno[i] != sccno[l]){
vis[sccno[l]] ++;
}
}
}
ll ans = 0;
for(int i = 1; i <= n; i ++){ /// 找出入度为零的强连通的花费和
if(sccno[i] == i && !vis[i]) ans += c[i];
}
printf("Case #%d: %I64d\n", ++ u, ans);
}
return 0;
}
思路是先对每个点的花费进行排序,然后从花费小的且没被搜到过的点开始DFS,除了一开始搜的点,其他都做上标记,如果搜到的是标记过的点那么返回,最后找出没标记过的点,输出这些点的花费和。
以下是代码(20个点以内找不到错的数据,点多了就错了,I don't know why??)
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int big = 1e3 + 7;
vector z[big];
struct node
{
ll x, y, r, c, num;
} a[big];
bool vis[big];
int record;
ll dis(ll kk, ll hh)
{
return kk*kk + hh*hh;
}
bool cmp(node u, node v)
{
return u.c < v.c;
}
void DFS(int key)
{
int tt = z[key].size(), ff;
for(int i = 0; i < tt; i ++)
{
ff = z[key][i];
if(!vis[ff] && record != ff) /// record != ff 是防止把开始搜的点给标记上了,因为有可能成环,record 记录了每次开始DFS时的点
{
vis[ff] = true;
DFS(ff);
}
}
}
int main()
{
int t, n, u = 0;
scanf("%d", &t);
while(t --)
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
{
scanf("%I64d %I64d %I64d %I64d", &a[i].x, &a[i].y, &a[i].r, &a[i].c);
a[i].num = i;
a[i].r *= a[i].r;
z[i].clear();
}
for(int i = 1; i < n; i ++) /// 建有向图
for(int j = i + 1; j <= n; j ++)
{
ll temp = dis(a[i].x - a[j].x, a[i].y - a[j].y);
if(temp <= a[i].r) z[i].push_back(j);
if(temp <= a[j].r) z[j].push_back(i);
}
memset( vis, false, sizeof( vis));
sort( a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; i ++) /// DFS
{
if(vis[a[i].num]) continue;
record = a[i].num;
DFS(record);
}
ll sum = 0;
for(int i = 1; i <= n; i ++) if(!vis[a[i].num]) sum += a[i].c;
printf("Case #%d: %I64d\n", ++ u, sum);
}
return 0;
}