题意

在一平面内存在多颗炸弹,每颗炸弹有属性位置(x,y),爆炸范围r,起爆花费c。

对于在起爆范围内的炸弹会连锁引爆,无花费。

求最小花费引爆所有炸弹。

思路

2016中国大学生程序设计竞赛的题目,图论题。

重现赛的时候队友用SPFA的思路WA了,就没有做下去。

寒假练习又看到了,重新思考了下,觉得有几条规律。

如果b在a的引爆范围,则添加a到b的有向边,可以得到一个有向图。

  • 对于入度为0的点是必须要起爆的。
  • 对于被上一条中的点引爆的点可以直接删去。
  • 对于出度为0的可以直接删去
  • 重复上一条,直到没有点被删除。

在删除确定的点后,剩下的显然都是环,在一个环内起爆一个最小花费点就可以了。

然而还是WA。

验证了下发现是漏了一个环指向另一个环的情况,被指向的环也是要被删除的。

于是对于一个环进行缩点,再删除出度为0的点。

这里题意原本大概是用Tarjan之类的算法求强连通分量。

但是本题比较特殊,所有的环都可以通过合并互相距离为1的点收缩为一个点。

例程

#include <cstdio>
#include <cstring>
#include <vector>
#include <bitset>
#include <algorithm>

using namespace std;

const int N = 1010;
typedef long long int64;

class Boom{
public:
    int r;
    int x;
    int y;
    int c;
    bool id;
    bool operator== (const Boom &a) const {
        return id == a.id;
    }
};
typedef vector<Boom>::iterator it;
vector<Boom> booms;
bitset<N> out[N];
bitset<N> in[N];
int n;

inline bool combo(Boom a,Boom b){
    if((a.r*(int64)a.r) >= 
            ((a.x-b.x)*(int64)(a.x-b.x) +
             (a.y-b.y)*(int64)(a.y-b.y)))
        return true;
    return false;
}

void shrink(int i){
    booms[i].id = false;
    for(int j=0;j<n;j++){
        if(booms[j].id && in[i][j]){
            in[i][j] = 0;
            out[j][i] = 0;
            if(out[j].none() && in[j].any()){
                shrink(j);
            }
        }
    }
}

int staining(int i){
    booms[i].id = false;
    int c = booms[i].c;
    for(int j=0;j<n;j++){
        if(booms[j].id && out[i][j]){
            c = min(c,staining(j));
        }
    }
    return c;
}

bool cmp(const Boom &a, const Boom &b) {
    if(a.id == b.id)return a.c < b.c;
    return a.id < b.id;
}

void merge(int a,int b) {
    booms[b].id = false;
    for(int i=0;i<n;i++){
        if(in[b][i]){
            out[i][b] = 0;
            out[i][a] = 1;
            in[a][i] = 1;
        }
        if(out[b][i]){
            in[i][b] = 0;
            in[i][a] = 1;
            out[a][i] = 1;
        }
    }
    in[a][a] = 0;
    out[a][a] = 0;
    for(int i=0;i<n;i++){
        if(booms[i].id){
            if(out[i][a] && out[a][i]){
                if(booms[i].c < booms[a].c)
                    merge(i,a);
                else
                    merge(a,i);
            }
        }
    }
}

int main(){
    int T,ans;
    Boom b;
    scanf("%d",&T);
    for(int t=1;t<=T;t++){
        booms.clear();
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%d%d%d%d",&b.x,&b.y,&b.r,&b.c);
            b.id = true;
            booms.push_back(b);
            out[i].reset();
            in[i].reset();
        }
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                if(combo(booms[i],booms[j])){
                    out[i][j] = 1;
                    in[j][i] = 1;
                }
                if(combo(booms[j],booms[i])){
                    out[j][i] = 1;
                    in[i][j] = 1;
                }
            }
        }
        for(int i=0;i<n;i++){
            if(booms[i].id){
                for(int j=i+1;j<n;j++){
                    if(out[i][j] && out[j][i]){
                        if(booms[i].c < booms[j].c)
                            merge(i,j);
                        else
                            merge(j,i);
                    }
                }
            }
        }
        for(int i=0;i<n;i++) {
            in[i][i] = 0;
            out[i][i] = 0;
        }
        for(int i=0;i<n;i++){
            if(booms[i].id && out[i].none() && in[i].any()){
                shrink(i);
            }
        }
        ans = 0;
        for(int i=0;i<n;i++){
            if(booms[i].id){
                ans += staining(i);
            }
        }
        printf("Case #%d: %d\n",t,ans);
    }
    return 0;
}