tarjan(hdu 5934)

题目链接
题意:给n个炸弹的坐标和爆炸半径,引爆代价。当另一个炸弹在一个炸弹的爆炸范围之内,则这个炸弹也会爆炸。求让所有炸弹爆炸的最小花费。
思路:根据炸弹之间的关系建图,比如a能引爆b就在a到b建立一条有向边。然后将图缩点,然后统计入度为0的点的爆炸代价,累加就是结果。
同一个强联通分量里面的点取价值最小的那个引爆,入度不为0的点肯定能由其他入度为0的点到达。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
using namespace std;
const int maxn=1e6+10;
int n;
struct zp
{
    LL x,y,r;
    int c;
} meg[maxn];
struct edge
{
    int u,v,next;
} node[maxn*10];
LL get_dis(zp a,zp b)
{
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
int head[maxn],cont,sk[maxn],insk[maxn],belong[maxn],top,tot,tim,low[maxn],dfn[maxn];
/*
dfn[i]代表dfs到i点的时间戳是多少
low[i]代表i节点或者在i节点后面访问到的节点的最小时间戳是多少
belong[i]代表将图缩点之后i点对应的节点标号
sk[i]代表栈
insk[i]代表i节点是否在栈中
*/
void init()
{
    memset(head,-1,sizeof(head));
    tim=tot=top=cont=0;
    memset(dfn,0,sizeof(dfn));
    memset(insk,0,sizeof(insk));
}
void add(int u,int v)
{
    node[cont].u=u,node[cont].v=v;
    node[cont].next=head[u];
    head[u]=cont++;
}
void tarjan(int u)//tarjan缩点
{
    dfn[u]=low[u]=++tim;//第一次访问到节点u
    sk[top++]=u,insk[u]=1;//入栈
    for(int i=head[u]; i+1; i=node[i].next)
    {
        int v=node[i].v;
        if(!dfn[v]) tarjan(v);//v节点没有被访问过               1
        if(insk[v]) low[u]=min(low[u],low[v]);//v在栈中     2
        /*
           2的if有两种执行条件:

           1.当1的if执行时也就是dfn[v]==0,执行完后如果v在栈中的话更新low[u],如果v不在栈中的话
           说明v是图的一个割点,又因为v是在u之后遍历的,所以low[v]是大于dfn[u]的所以没有必要更新。
           2.当1的if不执行时,就是dfn[v]!=0说明v节点已经被访问过了,如果v还在栈中,说明v是在u之前
           被访问的,更新一下low[u],如果v不在栈中就说明v和u不在同一个强联通分量里面,所以不用更新
        */
    }
    if(low[u]==dfn[u])//u不一定是割点,上面2的情况的后面的那种有可能u就不是割点,u只有v一个出点,并且和u不在同一个强联通块中
    {
        tot++;
        int tmp;
        do
        {
            tmp=sk[--top];
            belong[tmp]=tot;
            insk[tmp]=0;
        }
        while(u!=tmp);//在栈中u之前的点属于同一个块
    }
}
int deg[maxn];
void solve()
{
    memset(deg,0,sizeof(deg));
    for(int i=0; i<n; i++)
    {
        for(int j=head[i]; j+1; j=node[j].next)
        {
            int v=node[j].v;
            if(belong[i]==belong[v]) continue;//属于同一个块的不计算
            deg[belong[v]]++;//计算点的入度
        }
    }
    int ans=0;
    for(int i=1; i<=tot; i++)
    {
        if(!deg[i])//点的入度为0是则这个点必须被引爆,入度不为0的带你肯定能能被其他点引爆
        {
            int tmp=10010;
            for(int j=0; j<n; j++)
            {
                if(belong[j]==i)//同一个强联通块中的点找价值最小的引爆
                {
                    tmp=min(tmp,meg[j].c);
                }
            }
            ans+=tmp;//累计答案
        }
    }
    printf("%d\n",ans);
}
int main()
{
    int ncase;
    scanf("%d",&ncase);
    for(int ca=1; ca<=ncase; ca++)
    {
        init();
        scanf("%d",&n);
        for(int i=0; i<n; i++)
            scanf("%lld%lld%lld%d",&meg[i].x,&meg[i].y,&meg[i].r,&meg[i].c);
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                if(i==j) continue;
                if(get_dis(meg[i],meg[j])<=meg[i].r*meg[i].r)
                    add(i,j);
            }
        }
        for(int i=0; i<n; i++)
            if(!dfn[i])
                tarjan(i);
        printf("Case #%d: ",ca);
        solve();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值