1. 程式人生 > >HDU-5934 Bomb(Tarjan SSC)

HDU-5934 Bomb(Tarjan SSC)

HDU-5934 Bomb (Tarjan SSC)

這次訓練賽美滋滋遇到兩個比較水的圖論題我能寫,總算有用的圖論選手感到舒適~

/*雖然因為卡這題很久所以最後還是掉到銀牌線下面了,哎呀好氣啊。而且如果出得快的話我們還有時間寫一個二分D題的,氣哭哭*/

題目大意

座標系上有N個炸彈,第i個炸彈在座標(xi,yi)(x_i,y_i)爆炸,有半徑rir_i的範圍內(包括邊界)所有接觸到的其他炸彈都能被引爆,但是引爆第i個炸彈需要花費cic_i的錢,問最少需要花多少錢才能把所有的炸彈都引爆。

思路

第一時間想到的是強連通分量,強連通分量裡面的所有炸彈都能互相引爆,這樣只要找強連通分量裡面花費最少的點就行了。但是後來又想到如果是一條鏈或者是一條鏈上面帶環這樣的話,必須從鏈的一端引爆(不必再單獨引爆後面連著的強連通分量裡的所有點了)。

建圖是O(n2n^2)的,只要檢查每個節點之間的距離和半徑關係就可以了。

於是我先用一個普通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(n2n^2)。。。

程式碼

#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;
}

今天沒有感謝文章了,這個題完全就是我們自己過的,要感謝都感謝隊友們的栽培~