P4048 [JSOI2010]冷冻波

P4048 [JSOI2010]冷冻波

人生第一道紫题!debug巨久,码量巨大

题目

题目描述

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。

样例 #1

样例输入 #1
2 3 1
-100 0 100 3
100 0 100 5
-100 -10
100 10
110 11
5 5 10
样例输出 #1
5

思路+代码

第一步

判断是否为-1,并且预处理每一个巫妖能够攻击的精灵

读入分为三类,分别是巫妖,小精灵和树木(注意树木是有半径的)。

我们要先求出是否所有的小精灵都能够被杀死,如果不能杀死,就直接输出-1.

这是样例

我们首先想到的判断是这个精灵是否在某一个巫妖的攻击范围内,并且没有被树挡住,枚举每一个精灵,是否都被攻击到,否则输出-1。

于是,我开了3个结构体,分别存储巫妖,精灵和树。

struct jing{
	int num;
	double x;
	double y;
	bool dead;
}j[205];
struct jingling{
	int num;
	double x;
	double y;
};
struct yao{
	double x;
	double y;
	double r;
	int time;
}y[205];
vector<jingling> v[205];
struct tree{
	double x;
	double y;
	double r;
}t[205];

其中jingling结构体是专门存入vector中的,目的是存储每一个巫妖能够攻击到的所有精灵的编号

既然读入数据是建立在直角坐标系上的,那我们也用直角坐标系来解决精灵是否能够被攻击的问题

double xie;
bool flag=0,use=0;
jingling nw;
for(int i=1;i<=m;i++){
	use=0;
	for(int c=1;c<=n;c++){
		flag=0;
		if(juli(y[c].x,y[c].y,j[i].x,j[i].y)<=y[c].r){
			if(y[c].y==j[i].y){
				for(int l=1;l<=k;l++){
					if(abs(t[l].y-y[c].y)<t[l].r&&t[l].x>=min(y[c].x,j[i].x)&&t[l].x<=max(y[c].x,j[i].x)){
						flag=1;
						break;
					}
				}
			}
			else if(y[c].x==j[i].x){
				for(int l=1;l<=k;l++){
					if(abs(t[l].x-y[c].x)<t[l].r&&t[l].y>=min(y[c].y,j[i].y)&&t[l].y<=max(y[c].y,j[i].y)){
							flag=1;
							break;
						}
					}
				}
			else{
				xie=xielv(y[c].x,y[c].y,j[i].x,j[i].y);
				for(int l=1;l<=k;l++){
					if(pointline(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y)<t[l].r&&pd(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y,y[c].x,y[c].y,j[i].x,j[i].y)){
						flag=1;
						break;
					}
				}
			}
			if(flag==0){
				nw.x=j[i].x;
				nw.y=j[i].y;
				nw.num=j[i].num;
				v[c].push_back(nw);
				use=1;
			}
		}
	}
	if(use==0){
		cout<<-1;
		return 0;
	}
}

我们先枚举每一个精灵,然后对于每一个精灵,枚举所有的巫妖,然后将精灵与枚举到的巫妖连接起来,形成线段,判断是否在这个巫妖的攻击范围内。但是在直角坐标系中,我们将线段表示为一条解析式,然后判断是否被任意一棵树挡住了。

这是计算距离的函数

double juli(double x1,double y1,double x2,double y2){
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

那我们要如何求解析式呢?我写了两个函数来求解斜率和截距。

double xielv(double x1,double y1,double x2,double y2){
	return (double)(y1-y2)/(x1-x2);
}
double jieju(double k,double x1,double y1){
	return y1-(k*x1);
}

我们判断解析式是否被树木挡住时,需要写一个函数,求树木圆心到直线的距离(点到直线的距离公式),如果距离小于树木半径,则是被挡住了,即函数pointline

double pointline(double k,double b,double x1,double y1){
	double con=1/(k*k+1);
	double x2=(con*x1)+(con*k*y1)-(con*k*b);
	double y2=(con*k*x1)+(k*k*con*y1)+(con*b);
	return juli(x1,y1,x2,y2);
}

记得用double,否则精度会有问题。但是需要注意特判形成的直线是否与x轴或y轴平行。但是如果表示为解析式,会出现一种误判,就是这条线段的延长线经过一棵树,但是线段本身不经过。这时候我们就需要特判树木圆心到直线的垂足是否在线段上(因为精灵不可能在树的内部),即函数pd

bool pd(double k,double b,double x1,double y1,double x3,double y3,double x4,double y4){
	double con=1/(k*k+1);
	double x2=(con*x1)+(con*k*y1)-(con*k*b);
	double y2=(con*k*x1)+(k*k*con*y1)+(con*b);
	if(x2>=min(x3,x4)&&x2<=max(x3,x4)){
		if(y2>=min(y3,y4)&&y2<=max(y3,y4)){
			return true;
		}
	}
	return false;
}

这里有我造出的一组样例,包含了所有特殊情况

3 4 2
2 8 8 1
5 0 5 1
0 4 3 1
4 2
2 5
-2 4
-4 7
-2 8 2
3 -2 2

调试完这些各种特判,我们便完成了这道题的第一步,下面是这一步的代码

#include<bits/stdc++.h>
using namespace std;
const int inf=0x7fffffff;
struct jing{
	int num;
	double x;
	double y;
	bool dead;
}j[205];
struct jingling{
	int num;
	double x;
	double y;
};
struct yao{
	double x;
	double y;
	double r;
	int time;
}y[205];
vector<jingling> v[205];
struct tree{
	double x;
	double y;
	double r;
}t[205];
double juli(double x1,double y1,double x2,double y2){
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double xielv(double x1,double y1,double x2,double y2){
	return (double)(y1-y2)/(x1-x2);
}
double jieju(double k,double x1,double y1){
	return y1-(k*x1);
}
double pointline(double k,double b,double x1,double y1){
	double con=1/(k*k+1);
	double x2=(con*x1)+(con*k*y1)-(con*k*b);
	double y2=(con*k*x1)+(k*k*con*y1)+(con*b);
	return juli(x1,y1,x2,y2);
}
bool pd(double k,double b,double x1,double y1,double x3,double y3,double x4,double y4){
	double con=1/(k*k+1);
	double x2=(con*x1)+(con*k*y1)-(con*k*b);
	double y2=(con*k*x1)+(k*k*con*y1)+(con*b);
	if(x2>=min(x3,x4)&&x2<=max(x3,x4)){
		if(y2>=min(y3,y4)&&y2<=max(y3,y4)){
			return true;
		}
	}
	return false;
} 
int n,m,k;
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){
		scanf("%lf%lf%lf%lf",&y[i].x,&y[i].y,&y[i].r,&y[i].time);
	}
	for(int i=1;i<=m;i++){
		scanf("%lf%lf",&j[i].x,&j[i].y);
		j[i].num=i;
		j[i].dead=0;
	}
	for(int i=1;i<=k;i++){
		scanf("%lf%lf%lf",&t[i].x,&t[i].y,&t[i].r);
	}
	double xie;
	bool flag=0,use=0;
	jingling nw;
	for(int i=1;i<=m;i++){
		use=0;
		for(int c=1;c<=n;c++){
			flag=0;
			if(juli(y[c].x,y[c].y,j[i].x,j[i].y)<=y[c].r){
				if(y[c].y==j[i].y){
					for(int l=1;l<=k;l++){
						if(abs(t[l].y-y[c].y)<t[l].r&&t[l].x>=min(y[c].x,j[i].x)&&t[l].x<=max(y[c].x,j[i].x)){
							flag=1;
							break;
						}
					}
				}
				else if(y[c].x==j[i].x){
					for(int l=1;l<=k;l++){
						if(abs(t[l].x-y[c].x)<t[l].r&&t[l].y>=min(y[c].y,j[i].y)&&t[l].y<=max(y[c].y,j[i].y)){
							flag=1;
							break;
						}
					}
				}
				else{
					xie=xielv(y[c].x,y[c].y,j[i].x,j[i].y);
					for(int l=1;l<=k;l++){
						if(pointline(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y)<t[l].r&&pd(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y,y[c].x,y[c].y,j[i].x,j[i].y)){
							flag=1;
							break;
						}
					}
				}
				if(flag==0){
					nw.x=j[i].x;
					nw.y=j[i].y;
					nw.num=j[i].num;
					v[c].push_back(nw);
					use=1;
				}
			}
		}
		if(use==0){
			cout<<-1;
			return 0;
		}
	}

	return 0;
}

这一步完成后,我们vector数组v中已经有了每一个巫妖可以杀死的精灵编号,接下来,我们就要开始计算使用的最短时间了

第二步

统计最短时间
先来看看我的第一直觉(乱搞解法)

既然是求最短时间那么我就把时间从0开始枚举,直到所有精灵都被打完。我们枚举时间时,只要当前时间时这个巫妖冷却时间的倍数,这个巫妖就可以攻击一次。

代码我就不贴了,丑而且是错的。

证伪:

当一个巫妖1攻击了一个精灵,但这个精灵也可以被另一个巫妖2攻击到,若巫妖1先攻击了精灵,然而巫妖2先完成了它所有能攻击的精灵,此时巫妖1还有其他精灵没有攻击完,故如果开始时让巫妖2攻击这个精灵更优

正解!

先说明,正解是用最大网络流,所以呢我们先讲一讲最大网络流是什么,知道的可以先跳过

贴上代码
#include<bits/stdc++.h>
using namespace std;
const long long inf=0x7fffffff;
long long n,m,s,t;
struct edge{
	int from;
	int to;
	long long cap;//容量
	long long flow;//流量
};
vector<edge> e;//存所有边 
vector<int> g[210];//g[i]表示从i出发的所有边在e中的编号 
int d[210];//bfs序 
int cur[210];//当前弧优化 
bool vis[210];
queue<int> q;//求BFS序 
void init(int n){
	for(int i=0;i<=n-1;i++){
		g[i].clear();
	}
	e.clear();
}
void addedge(int u,int v,long long c){
	edge temp;
	temp.from=u;
	temp.to=v;
	temp.cap=c;
	temp.flow=0;
	e.push_back(temp);
	g[u].push_back(e.size()-1);
	temp.from=v;
	temp.to=u;
	temp.cap=0;
	e.push_back(temp);
	g[v].push_back(e.size()-1);
}
bool BFS(){
	memset(vis,0,sizeof(vis));
	q.push(s);
	d[s]=0;
	vis[s]=1;
	int tt;
	while(!q.empty()){
		tt=q.front();
		q.pop();
		for(int i=0;i<=g[tt].size()-1;i++){
			if(vis[e[g[tt][i]].to]==0&&e[g[tt][i]].cap>e[g[tt][i]].flow){
				vis[e[g[tt][i]].to]=1;
				d[e[g[tt][i]].to]=d[tt]+1;
				q.push(e[g[tt][i]].to);
			}
		}
	}
	return vis[t];
}
long long DFS(int x,long long a){
	if(x==t||a==0) return a;
	long long flow=0,f;
	for(int i=cur[x];i<=g[x].size()-1;i++){
		cur[x]=i;
		if(d[x]+1==d[e[g[x][i]].to]&&(f=DFS(e[g[x][i]].to,min(a,e[g[x][i]].cap-e[g[x][i]].flow)))>0){
			e[g[x][i]].flow+=f;
			e[g[x][i]^1].flow-=f;
			flow+=f;
			a-=f;
			if(a==0) break;
		}
	}
	return flow;
}
long long maxflow(){
	long long maxf=0;
	while(BFS()){
		memset(cur,0,sizeof(cur));
		maxf+=DFS(s,inf);
	}
	return maxf;
}
int main(){
	long long ca,cb,cc;
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	init(n);
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld",&ca,&cb,&cc);
		addedge(ca,cb,cc);
	}
	cout<<maxflow();
	return 0;
}

自认为写的挺好看

首先,在这类题目中,我们有一个源点和汇点,分别为s和t

比如说这一张有向图,我们令s=1,t=4,我们要求出最大的流量,打个比方,就是每条边的权值(容量)就是水流的最大速度,有很多条水管从自来水厂(s)流到你家里,问你家出水口(t)的最大流速

在这个图中,很显然答案是5(1->3->4(流量为2),1->2->5->4(流量为3))

这里只简单地介绍一下dinic算法,具体网络流的算法有很多,可以去网上查一下

我们每一次都用BFS跑一边图,记录下每个点的BFS序(层数)

如果BFS不能跑到汇点,就结束算法

然后我们跑一次DFS,找到一条通往汇点的路径,同时记录下路径上的最小限速,返回这条路径的流量,每一次回溯时,我们需要将这条路径上的已用流量统计出来,并在反边上加上已用的流量

这就是dinic的基本思路

本题最大流思路

首先我们要知道,当时间越长,打死的巫妖就一定更多。所以,此题的最短时间具有二分性。

我们取一个时间最大值,我取了4000000,如果当前时间可以继续减小,就对时间继续进行二分

我们新建两个点分别为源点和汇点,把巫妖和精灵看作是点,将巫妖连上对应可以攻击到的精灵,并且使边权都为一,同时将每一个精灵都连接到汇点上,边权也是一(因为每一个精灵只能被攻击一次),这时候,我们将所有巫妖连上源点,但是边权不再是一了,而是当前二分到的时间除以这个巫妖的冷却时间,得到的就是这个巫妖在当前二分时间内最多的攻击次数,将攻击次数当作边权。这之后,我们就得到了这样一个图

在这个图中,我们跑一次最大流,得到的最大流实际上就是在当前时间内能够打死的最多的精灵。

只要我们不断的二分,找到第一个可以打死所有精灵的时间,这道题就做出来了。

附上正解代码

有亿点点长

#include<bits/stdc++.h>
using namespace std;
const long long inf=0x7fffffff;
int s=1100,endt=1101;
struct jing{
	int num;
	double x;
	double y;
	bool dead;
}j[205];
struct jingling{
	int num;
	double x;
	double y;
};
struct yao{
	double x;
	double y;
	double r;
	int time;
//	vector<jing> v;
}y[205];
vector<jingling> v[205];
struct tree{
	double x;
	double y;
	double r;
}t[205];





struct edge{
	int from;
	int to;
	long long cap;//容量
	long long flow;//流量
};
vector<edge> e;//存所有边 
vector<int> g[2000];//g[i]表示从i出发的所有边在e中的编号 
int d[2000];//bfs序 
int cur[2000];//当前弧优化 
bool vis[2000];
queue<int> q;//求BFS序 
void init(int n){
	for(int i=0;i<=n-1;i++){
		g[i].clear();
	}
	e.clear();
}
void addedge(int u,int v,long long c){
	edge temp;
	temp.from=u;
	temp.to=v;
	temp.cap=c;
	temp.flow=0;
	e.push_back(temp);
	g[u].push_back(e.size()-1);
	temp.from=v;
	temp.to=u;
	temp.cap=0;
	e.push_back(temp);
	g[v].push_back(e.size()-1);
}
bool BFS(){
	memset(vis,0,sizeof(vis));
	memset(d,0,sizeof(d));
	q.push(s);
	d[s]=0;
	vis[s]=1;
	int tt;
	while(!q.empty()){
		tt=q.front();
		q.pop();
		for(int i=0;i<=g[tt].size()-1;i++){
			if(vis[e[g[tt][i]].to]==0&&e[g[tt][i]].cap>e[g[tt][i]].flow){
				vis[e[g[tt][i]].to]=1;
				d[e[g[tt][i]].to]=d[tt]+1;
				q.push(e[g[tt][i]].to);
			}
		}
	}
	return vis[endt];
}
long long DFS(int x,long long a){
	if(x==endt||a==0) return a;
	long long flow=0,f;
	for(int i=cur[x];i<=g[x].size()-1;i++){
		cur[x]=i;
		if(d[x]+1==d[e[g[x][i]].to]&&(f=DFS(e[g[x][i]].to,min(a,e[g[x][i]].cap-e[g[x][i]].flow)))>0){
			e[g[x][i]].flow+=f;
			e[g[x][i]^1].flow-=f;
			flow+=f;
			a-=f;
			if(a==0) break;
		}
	}
	return flow;
}
long long maxflow(){
	long long maxf=0;
	while(BFS()){
		memset(cur,0,sizeof(cur));
		maxf+=DFS(s,inf);
	}
	return maxf;
}




double juli(double x1,double y1,double x2,double y2){
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double xielv(double x1,double y1,double x2,double y2){
	return (double)(y1-y2)/(x1-x2);
}
double jieju(double k,double x1,double y1){
	return y1-(k*x1);
}
double pointline(double k,double b,double x1,double y1){
	double con=1/(k*k+1);
	double x2=(con*x1)+(con*k*y1)-(con*k*b);
	double y2=(con*k*x1)+(k*k*con*y1)+(con*b);
	return juli(x1,y1,x2,y2);
}
bool pd(double k,double b,double x1,double y1,double x3,double y3,double x4,double y4){
	double con=1/(k*k+1);
	double x2=(con*x1)+(con*k*y1)-(con*k*b);
	double y2=(con*k*x1)+(k*k*con*y1)+(con*b);
	if(x2>=min(x3,x4)&&x2<=max(x3,x4)){
		if(y2>=min(y3,y4)&&y2<=max(y3,y4)){
			return true;
		}
	}
	return false;
}
int n,m,k;
int main(){
	// freopen("cold.in","r",stdin);
	// freopen("cold.out","w",stdout);
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){
		scanf("%lf%lf%lf%d",&y[i].x,&y[i].y,&y[i].r,&y[i].time);
	}
	for(int i=1;i<=m;i++){
		scanf("%lf%lf",&j[i].x,&j[i].y);
		j[i].num=i;
		j[i].dead=0;
	}
	for(int i=1;i<=k;i++){
		scanf("%lf%lf%lf",&t[i].x,&t[i].y,&t[i].r);
	}
	double xie;
	bool flag=0,use=0;
	jingling nw;
	for(int i=1;i<=m;i++){
		use=0;
		for(int c=1;c<=n;c++){
			flag=0;
			if(juli(y[c].x,y[c].y,j[i].x,j[i].y)<=y[c].r){
				if(y[c].y==j[i].y){
					for(int l=1;l<=k;l++){
						if(abs(t[l].y-y[c].y)<t[l].r&&t[l].x>=min(y[c].x,j[i].x)&&t[l].x<=max(y[c].x,j[i].x)){
							flag=1;
							break;
						}
					}
				}
				else if(y[c].x==j[i].x){
					for(int l=1;l<=k;l++){
						if(abs(t[l].x-y[c].x)<t[l].r&&t[l].y>=min(y[c].y,j[i].y)&&t[l].y<=max(y[c].y,j[i].y)){
							flag=1;
							break;
						}
					}
				}
				else{
					xie=xielv(y[c].x,y[c].y,j[i].x,j[i].y);
					for(int l=1;l<=k;l++){
//						cout<<endl<<endl<<j[i].x<<" "<<j[i].y<<"   "<<y[c].x<<" "<<y[c].y<<"  "<<t[l].x<<" "<<t[l].y<<"  "<<pointline(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y)<<"  "<<pd(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y,y[c].x,y[c].y,j[i].x,j[i].y)<<endl<<endl;
						if(pointline(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y)<t[l].r&&pd(xie,jieju(xie,y[c].x,y[c].y),t[l].x,t[l].y,y[c].x,y[c].y,j[i].x,j[i].y)){
							flag=1;
							break;
						}
					}
				}
				if(flag==0){
					nw.x=j[i].x;
					nw.y=j[i].y;
					nw.num=j[i].num;
					v[c].push_back(nw);
					use=1;
				}
			}
		}
		if(use==0){
			cout<<-1;
			return 0;
		}
	}
	int l=0,r=4000000,mid;
	int ans;
	while(l<r){
		mid=(l+r)/2;
		init(n+m+2);
		for(int i=1;i<=n;i++){
//			if(v[i].size()>0){
				for(int w=0;w<v[i].size();w++){
					addedge(i,v[i][w].num+500,1);
				}
//			}
		}
		for(int i=1;i<=m;i++){
			addedge(i+500,endt,1);
		}
		for(int i=1;i<=n;i++){
			addedge(s,i,mid/y[i].time+1);
		}
		if(maxflow()==m){
			ans=mid;
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	cout<<ans;
	return 0;
}

本人第一道紫题,也是第一道紫题题解!

注意

本人特别喜欢使用vector,网上大多数最大流都是用的前向星,但是这一次栽在了vector上。

特别注意,使用vector时,用for循环遍历时,不要写i<=v.size()-1,而是写成i<v.size(),因为如果这个vector为空的话,-1之后unsigned long long会下溢出,直接报错,本人因为这个而run time error,debug一个晚上才改出来,一定要注意啊!

此片题解较长,恭喜你耐下性子看完了,写题解不易,希望给个赞

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值