「国庆训练」Bomb(HDU-5934)

题意

给定(n)个炸弹,每个炸弹的坐标与代价与影响范围给定,炸弹会引爆影响范围内其他所有炸弹。求引爆所有炸弹的最小代价。

分析

先做(n^2)的循环,然后建图,对(i)能引爆(j)建边((i,j))。然后对这个图求强连通分量并缩点,构成新的有向无环的森林。定义每个强连通分量的cost为其中包含的点的最小cost,然后把新森林中所有入度为0的点的cost加起来求和即可(由于无环,所以从任何入度不为0的点往回走,必然终止于一个入度为0的点)。

代码

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <stack>
#define MP make_pair
#define PB push_back
#define fi first
#define se second
#define ZERO(x) memset((x), 0, sizeof(x))
#define ALL(x) (x).begin(),(x).end()
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define per(i, a, b) for (int i = (a); i >= (b); --i)
#define QUICKIO                  
    ios::sync_with_stdio(false); 
    cin.tie(0);                  
    cout.tie(0);
#define MS(x,y) memset(x,y,sizeof(x))
#define int ll
using namespace std;
typedef long long ll;

const int MAXN=1005;
vector<int> G[MAXN];
bool mat[MAXN][MAXN];
int n;
int pre[MAXN], lowlink[MAXN], sccno[MAXN], dfs_clock, scc_cnt;
stack<int> stk;

void dfs(int u)
{
	pre[u]=lowlink[u] = ++dfs_clock;
	stk.push(u);
	rep(i,0,n-1)
	{
		if(!mat[u][i]) continue; 
		int v=i;
		if(!pre[v])
		{
			dfs(v);
			lowlink[u]=min(lowlink[u],lowlink[v]);
		}
		else if(!sccno[v])
		{
			lowlink[u]=min(lowlink[u],pre[v]);
		}
	}
	if(lowlink[u]==pre[u])
	{
		scc_cnt++;
		for(;;)
		{
			int x=stk.top(); stk.pop();
			sccno[x]=scc_cnt;
			if(x==u) break;
		}
	}
}

void find_scc()
{
	dfs_clock=scc_cnt=0;
	ZERO(sccno);
	ZERO(pre);
	rep(i,0,n-1)
		if(!pre[i]) dfs(i);
}

bool nmat[MAXN][MAXN];

vector<pair<int,int> > edges;
vector<int> nG[MAXN];
int ncnt=0;

void add_edges(int u,int v)
{
	edges.PB(MP(u,v));
	nG[u].PB(edges.size()-1);
}

pair<int,int> pnt[MAXN];
int pntc[MAXN], pntr[MAXN];

inline double dist(int x,int y)
{
	return sqrt((pnt[x].fi-pnt[y].fi)*(pnt[x].fi-pnt[y].fi)+
				(pnt[x].se-pnt[y].se)*(pnt[x].se-pnt[y].se));
}
int cost[MAXN];
signed main()
{
	int T; scanf("%lld", &T);
	rep(kase,1,T)
	{
		ZERO(nmat);
		ZERO(mat);
		scanf("%lld", &n);
		rep(i,1,n)
		{
			int x,y;
			scanf("%lld%lld%lld%lld", &x,&y, &pntr[i], &pntc[i]);
			pnt[i]=MP(x,y);
		}
		rep(i,1,n)
		{
			rep(j,1,n)
			{
				if(i==j) continue;
				double d=pntr[i]-dist(i,j);
				if(fabs(d)<1e-6 || d>1e-6)
				{
					mat[i-1][j-1]=true;
				}
			}
		}
		/*
		rep(i,0,n-1)
		{
			rep(j,0,n-1)
				cout<<mat[i][j]<<" ";
			cout<<endl;
		}
		*/
		find_scc();
		memset(cost,0x3f,sizeof(cost));
		rep(i,1,n)
		{
			cost[sccno[i-1]]=min(cost[sccno[i-1]],pntc[i]);
		}
		rep(i,0,n-1)
		{
			rep(j,0,n-1)
			{
				if(i==j) continue;
				nmat[sccno[i]][sccno[j]]|=
					mat[i][j];
			}
		}
		/*
		rep(i,1,scc_cnt)
		{
			rep(j,1,scc_cnt)
				cout<<nmat[i][j]<<" ";
			cout<<endl;
		}
		*/
		ll ans=0;
		/*
		rep(i,0,n-1) cout<<sccno[i]<<" ";
		cout<<endl;
		rep(i,0,n-1) cout<<cost[i]<<" ";
		cout<<endl;
		*/
		
		rep(i,1,scc_cnt)
		{
			int ok=0;
			rep(j,1,scc_cnt)
			{
				if(i==j) continue;
				if(nmat[j][i])
				{
					ok++;
					break;
				}
			}
			if(!ok)
			{
				//cout<<i<<" "<<cost[i]<<endl;
				ans+=cost[i];
			}
		}
		printf("Case #%lld: %lld
", kase, ans);
	}
	return 0;
}

札记

这题是在一场训练赛中打的。当时的我们激情卡题两个半小时23333然后我觉得不行了只能换题,不懂图论的队友说了这题可以写,他觉得是带权并查集23333我想了一下,这一看就是缩点啊。然后缩点之后没什么好办法,不过也没浪费时间——他们还在卡题23333过了又是半个小时,他们终于出了另外一题(卡的那个签到题还是没出!!!),这个时候还剩下一个半小时了,我想到可以求和入度为0的点即可。然后又过去半个小时(出签到题啊啊啊啊啊)没出(- -|||),只好我上写这题,然后半个小时写完,5分钟调试,交上去WA,看了下代码,改了个long long,过了。后来那个签到题成功出了(太真实了),我们翻盘大成功,哇咔咔~

如非注明,原创内容遵循GFDLv1.3发布;其中的代码遵循GPLv3发布。
原文地址:https://www.cnblogs.com/samhx/p/HDU-5934.html