[JSOI2010]冷冻波题解

题目描述

WJJ喜欢“魔兽争霸”这个游戏。在游戏中,巫妖是一种强大的英雄,它的技能Frozen Nova每次可以杀死一个小精灵。我们认为,巫妖和小精灵都可以看成是平面上的点。

当巫妖和小精灵之间的直线距离不超过R,且巫妖看到小精灵的视线没有被树木阻挡(也就是说,巫妖和小精灵的连线与任何树木都没有公共点)的话,巫妖就可以瞬间杀灭一个小精灵。

在森林里有N个巫妖,每个巫妖释放Frozen Nova之后,都需要等待一段时间,才能再次施放。不同的巫妖有不同的等待时间和施法范围,但相同的是,每次施放都可以杀死一个小精灵。

现在巫妖的头目想知道,若从0时刻开始计算,至少需要花费多少时间,可以杀死所有的小精灵?

输入

输入文件第一行包含三个整数N、M、K(N,M,K<=200),分别代表巫妖的数量、小精灵的数量和树木的数量。

接下来N行,每行包含四个整数x, y, r, t,分别代表了每个巫妖的坐标、攻击范围和施法间隔(单位为秒)。

再接下来M行,每行两个整数x, y,分别代表了每个小精灵的坐标。

再接下来K行,每行三个整数x, y, r,分别代表了每个树木的坐标。

输入数据中所有坐标范围绝对值不超过10000,半径和施法间隔不超过20000。

输出

输出一行,为消灭所有小精灵的最短时间(以秒计算)。如果永远无法消灭所有的小精灵,则输出-1。

样例输入

2 3 1
-100 0 100 3
100 0 100 5
-100 -10
100 10
110 11
5 5 10

样例输出

5

题解

这道题思路不是很难,但是实现起来有点繁,容易错。

先读题,有巫妖、精灵和树三类角色,都有确定的位置。巫妖能对一定范围内的精灵造成一击必杀,但是要满足两个条件:巫妖与精灵的距离小于巫妖的射程、巫妖与精灵的连线没有与任何一个树(一个圆)相交,我个人觉得相切是OK的(原来的数据也没有卡这个,可能是因为不好判断,也没有准确的判断)。同时巫妖有CD(CD:技能冷却。注意t=0时巫妖可以直接发出攻击),原题问最少需要多少时间能击杀所有精灵,或者能够知道根本无法全部击杀。

可以很容易的知道,这道题的一个难点是找出一个巫妖能够对哪些精灵进行攻击。不妨枚举所有的巫妖,然后对于每一个巫妖,枚举每一个精灵。首先计算出巫妖和精灵的距离rs。如果此时距离已经大于攻击范围,就直接continue。否则就需要判断是否有树阻挡了。

如何判断树是否有影响?

 

如图所示,A为巫妖的位置,B为精灵的位置,C为圆心,是树的位置。现在过圆心C作线段AB垂线,垂足为H 。如果说树能够造成阻挡,那么分为两种情况:一种是H在线段AB上,如上图。显然直接判断CH的长度是否大于圆的半径即可。我们假设几个变量:直线AB方程为y=k1*x+b1,直线CH方程为y=k2*x+b2,点H的坐标为(x1,y1),其余变量用如XA,YB来表示已知点的横纵坐标。已知A、B两点的坐标,可以在枚举树之前就预处理出直线AB的解析式,显然k1、b1已知。由于AB\perpCH,所以k1*k2=-1,通过这个关系式求出k2。由于C点坐标已知,就可以得到直线CH的解析式,b2已知。因此可以联立方程解出点H的坐标(x1,y1)。就代码层面来说都是O(1)的,代码中的geometry(几何)函数有求解的过程,以供参考。求出H坐标后,由于H在线段AB上,此刻一定满足H的横坐标一定在A、B之间(A、B左右关系不明确,可用max、min函数来判断。以上对于纵坐标同样成立)。这时用两点坐标公式求出线段CH的长度,与半径比大小。如果大于等于半径,说明树无法造成阻挡,反之即可。

 当然也可能出现垂足不在线段上的情况(毕竟不是直线),对于这第二种情况,判断方式就是H的横坐标不在A、B之间。这样直接找AC、BC中较小的一个,判断与半径的大小关系。如果最小的线段都比半径大,说明树根本阻拦不到,否则树可以阻拦。

通过以上步骤,可以确定哪一个巫妖可以攻击哪些精灵。如果有精灵无法被任意一个巫妖攻击到,那么输出-1无解。

建议了解网络流的基本知识以及基本代码再阅读下面的内容。

如果所有精灵都能被攻击到,那么只用计算最小时间。翻译一下题目,就是:每一个巫妖可以对指定的精灵造成攻击力为1点的伤害(一击致命),每一个精灵也只能承受一点攻击力。如果假设每一个精灵的殒命都能对一个“英雄”提供一点经验点,那么当经验点为精灵数时,代表所有精灵都被OK掉,此时的时间为答案。于是可以用网络流,由于在满足条件的情况下,一定能找到从源点流向汇点的最大流为精灵数,唯一需要考虑的是能不能在某一个时间内攻击到所有精灵(正是因此才要二分)。首先建立源点通向每一个巫妖,边权为每一个巫妖能够攻击的精灵数,表示总攻击力(放心,根据后面建的边,不会攻击到其他的精灵,所以总攻击力可能用完,也可能有剩余),然后根据巫妖与精灵的相互关系(从巫妖指向精灵)再建部分边,边权为1,表示一点攻击力。最后让所有精灵都指向汇点,边权还是1,表示对“英雄”提供的经验值(假设的,方便理解)。

因此完全可以二分总时间,直接判断出每一个巫妖能流出多少攻击力,再判断杀死的精灵数是否是所有的精灵数。由于求的是最短时间,因此满足条件更新右端点,否则更新左端点。枚举好时间之后就可以建边了。注意t=0时也可以攻击一次,所以建的巫妖的边权要+1。然后直接最大流,然后所有巫妖用尽洪荒之力能否在规定时间内搞定所有精灵。注意,之所以选择网络流,是因为这是以一种比较优的效率求出最优的路线,来求出可得的最大流。具体的写法是由一个bfs、一个dfs解决,此时就不赘述了。bfs找路径,dfs搜最大流。注意建反边时边权初始为0。

参考代码

#include<queue>
#include<cstdio>
#include<cstring>
#define EXP 0.0000001
#define INF 999999999
using namespace std;
struct tree
{
	int nxt,to,dis;
}tr[120001];
int head[120001],cnt=-1;
struct pos1
{
	double x,y,r;int t;
}a[2001];
struct pos2
{
	double x,y;
}b[2001];
struct pos3
{
	double x,y,r;
}c[2001];
queue<int>q;int dis[120001];
int n,m,k,lis[2001][2001],nus=0,mx=0;
double max_d(double p,double q) { return p-q>EXP?p:q; }
double min_d(double p,double q) { return p-q<EXP?p:q; }
int max1(int p,int q) { return p>q?p:q; }
int min1(int p,int q) { return p<q?p:q; }
void geometry()
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int pd=0;double k1,b1,rs,k2,b2,x1,y1;
			k1=(a[i].y-b[j].y)/(a[i].x-b[j].x);
			b1=a[i].y-k1*a[i].x;
			rs=(a[i].x-b[j].x)*(a[i].x-b[j].x)+
			   (a[i].y-b[j].y)*(a[i].y-b[j].y);
			if(rs-a[i].r*a[i].r>EXP) continue;
			k2=-1.0/k1;
			for(int z=1;z<=k;z++)
			{
				b2=c[z].y-k2*c[z].x;
				x1=(b2-b1)/(k1-k2);
				y1=k1*x1+b1;
				if(y1-max_d(a[i].y,b[j].y)>EXP||y1-min_d(a[i].y,b[j].y)<EXP)
				{
					double r1,r2;
					r1=(a[i].x-c[z].x)*(a[i].x-c[z].x)+(a[i].y-c[z].y)*(a[i].y-c[z].y);
					r2=(b[j].x-c[z].x)*(b[j].x-c[z].x)+(b[j].y-c[z].y)*(b[j].y-c[z].y);
					if(min_d(r1,r2)-c[z].r*c[z].r<EXP) pd=1;
				}
				else
				{
					double rt;
					rt=(c[z].x-x1)*(c[z].x-x1)+(c[z].y-y1)*(c[z].y-y1);
					if(rt-c[z].r*c[z].r<EXP) pd=1;
				}
				if(pd==1) break;
			}
			if(pd==0) 
			{
				lis[j][++lis[j][0]]=i;
				if(lis[j][0]==1) nus--;
			}
		}
	}
}
void build_tree(int u,int v,int d)
{
	tr[++cnt].nxt=head[u];
	tr[cnt].to=v;
	tr[cnt].dis=d;
	head[u]=cnt;
	tr[++cnt].nxt=head[v];
	tr[cnt].to=u;
	tr[cnt].dis=0;
	head[v]=cnt;
}
void init(int x)
{
	for(int i=1;i<=n;i++) build_tree(m+n+2,i,x/a[i].t+1);
	for(int i=1;i<=m;i++) build_tree(i+n,m+n+1,1);
	for(int i=1;i<=m;i++)
	for(int j=1;j<=lis[i][0];j++)
	build_tree(lis[i][j],i+n,1);
}
int bfs()
{
	memset(dis,-1,sizeof(dis));
	dis[m+n+2]=0;
	q.push(m+n+2);
	while(!q.empty())
	{
		int pt=q.front();q.pop();
		for(int i=head[pt];i;i=tr[i].nxt)
		{
			int to=tr[i].to;
			if(dis[to]==-1&&tr[i].dis)
			{
				dis[to]=dis[pt]+1;
				q.push(to);
			}
		}
	}
	return dis[m+n+1]!=-1;
}
int dfs(int k,int flow)
{
	if(k==m+n+1) return flow;
	int delta=flow;
	for(int i=head[k];i;i=tr[i].nxt)
	{
		int to=tr[i].to;
		if(dis[to]==(dis[k]+1)&&(tr[i].dis)>0&&delta>0)
		{
			int d=dfs(to,min1(tr[i].dis,delta));
			tr[i].dis-=d;
			delta-=d;tr[i^1].dis+=d;
		}
	}
	if(delta==flow) dis[k]=-1;
	return flow-delta;
}
int dinic()
{
	int ans=0;
	while(bfs()) ans+=dfs(m+n+2,INF);
	return ans;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf%lf%d",&a[i].x,&a[i].y,&a[i].r,&a[i].t);
		mx=max1(mx,a[i].t);
	}
	for(int i=1;i<=m;i++)
	scanf("%lf%lf",&b[i].x,&b[i].y);
	for(int i=1;i<=k;i++)
	scanf("%lf%lf%lf",&c[i].x,&c[i].y,&c[i].r);
	nus=m;
	geometry();
	if(nus) 
	{
		printf("-1");
		return 0;
	}
	int l=0,r=mx*m;
	while(l<r)
	{
		int mid=(l+r)/2;cnt=1;
		memset(head,0,sizeof(head));
		init(mid);
		if(dinic()==m) r=mid;
		else l=mid+1;
	}
	printf("%d",l);
	return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值