HDU-5934 Bomb(Tarjan SSC)

HDU-5934 Bomb (Tarjan SSC)

这次训练赛美滋滋遇到两个比较水的图论题我能写,总算有用的图论选手感到舒适~

/*虽然因为卡这题很久所以最后还是掉到银牌线下面了,哎呀好气啊。而且如果出得快的话我们还有时间写一个二分D题的,气哭哭*/

题目大意

坐标系上有N个炸弹,第i个炸弹在坐标 ( x i , y i ) (x_i,y_i) (xi,yi)爆炸,有半径 r i r_i ri的范围内(包括边界)所有接触到的其他炸弹都能被引爆,但是引爆第i个炸弹需要花费 c i c_i ci的钱,问最少需要花多少钱才能把所有的炸弹都引爆。

思路

第一时间想到的是强连通分量,强连通分量里面的所有炸弹都能互相引爆,这样只要找强连通分量里面花费最少的点就行了。但是后来又想到如果是一条链或者是一条链上面带环这样的话,必须从链的一端引爆(不必再单独引爆后面连着的强连通分量里的所有点了)。

建图是O( n 2 n^2 n2)的,只要检查每个节点之间的距离和半径关系就可以了。

于是我先用一个普通DFS(从入度为0的点开始)把所有的带链的情况都引爆了,再用一个Tarjan求强连通分量求每个分量里面最小的去引爆,成功WA了。

中国好队友王大浑同志(今天的最佳辅助)成功找到了一组反例,如下:

Input

1

4

0 1 1 1

0 3 2 2

2 3 1 3

-2 3 4 4

Output

Case #1: 4

这个例子我的程序是把第二个点引爆了(因为是第二个点和第四个点的强连通分量里面比较小的),然后又把第一个点和第三个点给引爆了(它们各自是自己所在强连通分量唯一的节点),最终输出了6 。实际上这里的第四个节点已经能够把其余三个节点都引爆了。

恍然大悟,那么这道题的真实思路是这样的:先做一遍Tarjan强连通缩点(这样一来图就变成了无环的图),然后再用普通DFS去引爆入度为0的所有点就可以了,也就是我之前做法反一反。(等等,好像不需要DFS,只需要找入度为0的点不就可以了么???!!)

缩点以后新的节点的cost值就是原先强连通分量里面费用最小的点。

因为Tarjan的复杂度是O(n)的,所以整体复杂度其实是建图那里的O( n 2 n^2 n2)。。。

代码

#include <bits/stdc++.h>
using namespace std;
long long ans;
bool vis[1005];
bool vis2[1005];
stack<long long> ss;
long long timestamp[1005];
long long low[1005];
long long now;
vector<long long> egs[1005];
long long cost[1005];
long long degree[1005];
long long scc[1005];
long long mapper[1005][1005];
vector<long long> sccs;
struct nodes
{
    long long x;
    long long y;
    long long r;
    long long ind;
}no[1005];
void tarjan(long long num)
{
    now++;
    timestamp[num]=now;
    low[num]=now;
    vis[num]=true;
    ss.push(num);
    for(long long i=0;i<egs[num].size();i++)
    {
        long long nxt=egs[num][i];
        if(timestamp[nxt]==0)
        {
            tarjan(nxt);
            if(low[nxt]<low[num]){
                low[num]=low[nxt];
            }
        }
        else
        {
            if(vis[nxt] && timestamp[nxt]<low[num]){
                low[num]=timestamp[nxt];
            }
        }
    }
    if(timestamp[num]==low[num])
    {
        long long miner=0x3f3f3f3f3f3f3f3f;
        while(ss.top()!=num)
        {
            long long temp=ss.top();
            miner=min(miner,cost[temp]);
            vis2[temp]=true;//这个其实没有必要了,本来是用来防止普通DFS重复遍历的
            scc[temp]=num;//标记缩完点以后原来点的新编号
            vis[temp]=false;
            ss.pop();
        }
        miner=min(miner,cost[num]);
        scc[num]=num;
        cost[num]=miner;
        sccs.push_back(num);//标记新图里面有哪些节点,防止第二次DFS遍历旧图里的其他点。
        ss.pop();
        vis[num]=false;
    }
}
void dfs(long long num)//新图的DFS,其实没有用,只要找到入度为0的点直接引爆就行了
{
    if(vis2[num])
    {
        return;
    }
    vis2[num]=true;
    for(auto i:sccs)
    {
        if(mapper[num][i])
            dfs(i);
    }
}
void solve(long long ca)
{
    long long n;
    scanf("%lld",&n);
    ans=0;
    memset(timestamp,0,sizeof timestamp);
    memset(low,0,sizeof low);
    memset(vis,0,sizeof vis);
    memset(vis2,0,sizeof vis2);
    memset(degree,0,sizeof degree);
    memset(mapper,0,sizeof mapper);
    memset(scc,0,sizeof scc);
    sccs.clear();

    while (!ss.empty())
        ss.pop();
    for(long long i=1;i<=n;i++)
    {
        egs[i].clear();
        scanf("%lld%lld%lld%lld",&no[i].x,&no[i].y,&no[i].r,&cost[i]);
        if(no[i].r<0)
        {
            no[i].r=0;//题目里面说r的范围可以为负,以防万一先特判去了
        }
        no[i].ind=i;//其实没用。。。
    }
    for(long long i=1;i<=n;i++)
    {
        for(long long j=i+1;j<=n;j++)
        {
            if(i==j)
            {
                continue;
            }
            long long l=(no[i].x-no[j].x)*(no[i].x-no[j].x)+(no[i].y-no[j].y)*(no[i].y-no[j].y);//判断在不在引爆范围内,避免一个double的运算损失精度
//邻接表建旧图
            if(l<=no[i].r*no[i].r)
            {
                egs[i].push_back(j);
            }
            if (l<=no[j].r*no[j].r){
                egs[j].push_back(i);
            }
        }
    }
    for(long long i=1;i<=n;i++)
    {
        if(timestamp[i]==0)
        {
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<egs[i].size();j++)
        {
            int nxt=egs[i][j];
            if(scc[i]!=scc[nxt])
            {
                mapper[scc[i]][scc[nxt]]=true;//临界矩阵建新图(防止边重复,反正数据小)
                degree[scc[nxt]]++;//入度增加,等会要引爆入度为0的点
            }
        }
    }
    for(auto i:sccs)
    {
        if(!vis2[i] && degree[i]==0)
        {
            ans+=cost[i];
            dfs(i);//讲道理这个可以不要。。。算了反正没有T
        }
    }

    printf("Case #%lld: %lld\n",ca,ans);
}
int main()
{
    //freopen("test.txt","r",stdin);
    long long t;
    scanf("%lld",&t);
    for(long long i=1;i<=t;i++)
    {
        solve(i);
    }
    return 0;
}

今天没有感谢文章了,这个题完全就是我们自己过的,要感谢都感谢队友们的栽培~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值