HDU-5934 Bomb(Tarjan SSC)
HDU-5934 Bomb (Tarjan SSC)
這次訓練賽美滋滋遇到兩個比較水的圖論題我能寫,總算有用的圖論選手感到舒適~
/*雖然因為卡這題很久所以最後還是掉到銀牌線下面了,哎呀好氣啊。而且如果出得快的話我們還有時間寫一個二分D題的,氣哭哭*/
題目大意
座標系上有N個炸彈,第i個炸彈在座標爆炸,有半徑的範圍內(包括邊界)所有接觸到的其他炸彈都能被引爆,但是引爆第i個炸彈需要花費的錢,問最少需要花多少錢才能把所有的炸彈都引爆。
思路
第一時間想到的是強連通分量,強連通分量裡面的所有炸彈都能互相引爆,這樣只要找強連通分量裡面花費最少的點就行了。但是後來又想到如果是一條鏈或者是一條鏈上面帶環這樣的話,必須從鏈的一端引爆(不必再單獨引爆後面連著的強連通分量裡的所有點了)。
建圖是O()的,只要檢查每個節點之間的距離和半徑關係就可以了。
於是我先用一個普通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()。。。
程式碼
#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; }
今天沒有感謝文章了,這個題完全就是我們自己過的,要感謝都感謝隊友們的栽培~