人生第一道紫题!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一个晚上才改出来,一定要注意啊!
此片题解较长,恭喜你耐下性子看完了,写题解不易,希望给个赞
标签:JSOI2010,int,double,long,y1,x1,冷冻,P4048,con From: https://www.cnblogs.com/GXYZY/p/16869463.html