POJ 3308 Paratroopers(最小割EK(邻接表&矩阵))
Description
It is year 2500 A.D. and there is a terrible war between the forces of the Earth and the Mars. Recently, the commanders of the Earth are informed by their spies that the invaders of Mars want to land some paratroopers in them× n grid yard
of one their main weapon factories in order to destroy it. In addition, the spies informed them the row and column of the places in the yard in which each paratrooper will land. Since the paratroopers are very strong and well-organized, even one of them, if
survived, can complete the mission and destroy the whole factory. As a result, the defense force of the Earth must kill all of them simultaneously after their landing.
In order to accomplish this task, the defense force wants to utilize some of their most hi-tech laser guns. They can install a gun on a row (resp. column) and by firing this gun all paratroopers landed in this row (resp. column) will die. The cost of installing
a gun in the ith row (resp. column) of the grid yard is ri (resp.ci ) and the total cost of constructing a system firing all guns simultaneously is equal to the product of their costs. Now, your team as a high
rank defense group must select the guns that can kill all paratroopers and yield minimum total cost of constructing the firing system.
Input
Input begins with a number T showing the number of test cases and then,T test cases follow. Each test case begins with a line containing three integers 1 ≤m ≤ 50 , 1 ≤n ≤ 50 and 1 ≤l ≤ 500 showing the number of
rows and columns of the yard and the number of paratroopers respectively. After that, a line withm positive real numbers greater or equal to 1.0 comes where theith number isri and then, a line withn positive real
numbers greater or equal to 1.0 comes where theith number isci. Finally,l lines come each containing the row and column of a paratrooper.
Output
For each test case, your program must output the minimum total cost of constructing the firing system rounded to four digits after the fraction point.
Sample Input
1
4 4 5
2.0 7.0 5.0 2.0
1.5 2.0 2.0 8.0
1 1
2 2
3 3
4 4
1 4
Sample Output
16.0000
参考博客:http://blog.csdn.net/lyy289065406/article/details/6784658
这里用的EK
题意:
火星人侵略地球,他们意图登陆破坏某个地区的兵器工厂。据探子回报,火星人登陆的地区为n*m大小的地域,而且每一个火星人的着陆点坐标已知。
火星人很强悍,只要有一个火星人着陆后能够幸存,他必定能毁坏这片区域的全部兵工厂。为了防止这种情况发生,必须保证在火星人着陆的一瞬间把他们全部同时杀死。
现在防卫队有一个激光枪,开一枪就能把 在同一行(或同一列)着陆的火星人全部杀死。但是这种激光枪的使用是有代价的,把这种激光枪安装到不同行的行首、或者不同列的列首,费用都不同。现在已知把激光枪安装到任意位置的费用,总的花费为这些安装了激光枪的行列花费的乘积。
问怎样安装激光枪才能在杀死所有火星人的前提下费用最少?
题解:
1、 构造二分图
构造方法按照上述把“顶点覆盖问题转化为最小割问题”的方法去处理:
显然取行坐标为二分图的X集合,编号为1~N,点权就是激光炮在第i行射一炮的费用ri;列坐标为二分图的Y集合,编号为N+1~N+M,点权就是激光炮在第j列射一炮的费用cj。
然后建立超级源S,编号为0,超级汇T,编号为N+M+1。S向X集合每个点都连一条正向弧,边容量为第i点的点权;Y集合每个点都向T连一条正向弧,边容量为第j点的点权。而落有伞兵火星人的区域,表示该位置的x与y是连通的,则从X集合取该点与Y集合的对应点连一条正向弧,边容量为无限大inf。
X集合每个点到S的反向弧、T到Y集合每个点的反向弧,落有火星人区域的y 到x的反向弧,也都要连上边(这是为了后续的Dinic算法在增广链上调整流量之用),但边容量默认为0,表示不连通。
2、 此时问题转化为最小割问题,因为图G的最小割的容量,等于其最大流的流量,因此用求最大流的方法去求解。
但本题数据比较BT,常规求最大流的方法(压入重标法)会TLE,因此只能用相对更高效的Dinic算法。循环:一次BFS对残余图分层,一次DFS在层次图上求一条增广链,调整最大流。不懂Dinic的同学建议先去查阅相关资料,边学边做吧!
注意:
1、 double精度的问题。
本题有一句这样的话:the total cost of constructing a system firing all guns simultaneously is equal to the product of their costs.
其中product不是“产品”的意思,而是“乘积”的意思,英语差的同学建议查字典。
因此为了方便求最大流,应该首先对所有费用(点权)求一次自然对数,把乘法转变为加法。最后再对累加的最小费用求一次exp,就是答案。
而自然对数log是double型的, double精度在15~16位左右,那么本题的无限大inf和最小精度eps的差距就不能超过15位,否则精度问题会把你WA成sb。
而又由于,eps要开到输出小数位数两倍的原则(本题要求取到4位小数),那么eps在本题中开到1e-8就是很有必要的一件事情,所以相应的inf最多只能开到1e8。(但其实呢,2倍原则一般针对带有乘除法的浮点型问题,所以本题只开到1e-5或者1e-6亦可)
但注意本题中的另一个性质,取对数,任何数字取对数log之后就会变得很小(别告诉我你不知道O(logN)的算法有多么快),所以这题的inf开的很小就好。(inf开到1e2都能过,说明poj还比较仁慈,没有添加什么超2^100的数据。)
2、 存储问题。
本题推荐用邻接链表存储,邻接矩阵不可能不超时的。还有,上面构图时已经提及过了,在把二分图构造为单源单汇网络流时,看似都是只有一个方向的有向边(正向弧),但其实反向弧也要用到的(Dinic算法),因此往链表添加边a->b时,若不顺便添加边b->a,必然会WA。
3、poj的C++ 和G++的问题。
对于双精度输出,G++上面要用%f,C++则用%lf,否则WA。
试试证明,由于m,n都小于50,所以还是第一种用数组存储的时间快
第一种数组(适用于密集型):
#include <iostream>
#include <stdio.h>
#include <queue>
#include <cmath>
#include <string.h> #define INF 0x3f3f3f3f
#define FINF 99999999.0
#define N 110
int m,n;
using namespace std; double cap[N][N]; ///边容量
double flow[N][N]; ///边实际流量
int pre[N]; ///记录增广路径
double res[N]; ///残余网络
queue <int> que; void init()
{
while(!que.empty()) que.pop(); ///清空队列
memset(cap,0,sizeof(cap));
memset(flow,0,sizeof(flow));
} void EK(int s)
{
double ans=0;
int t = m+n+1;
while(true)
{
memset(res,0,sizeof(res));
res[s] = FINF;///源点的残留网络要置为无限大!否则下面找增广路出错
pre[s] = -1;
que.push(s);
///bfs找增广路径
while(!que.empty())
{
int x = que.front();
que.pop();
for(int i=0; i<=t ; i++)
{
if( !res[i] && flow[x][i] < cap[x][i] )
{
que.push(i);
pre[i] = x;
res[i] = min(cap[x][i] - flow[x][i], res[x]);///这里类似dp,如果有增广路,那么res[t]就是增广路的最小权
}
}
}
if(res[t]==0) break;///找不到增广路就退出
// printf("%f---",ans);
for(int k=m+n+1; pre[k]!=-1; k=pre[k])
{
flow[ pre[k] ][ k ] += res[t];///正向边加上新的流量
flow[ k ][ pre[k] ] -= res[t];///反向边要减去新的流量,反向边的作用是给程序一个后悔的机会
}
ans += res[t];
}
printf("%.4lf\n",exp(ans));
}
int main()
{
int cas;
int a,b;
double xx[55],yy[55];
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&m,&cas);
init();
for(int i=1; i<=n; i++)
{
scanf("%lf",xx+i);
xx[i]=log(xx[i]); }
for(int i=1; i<=m; i++)
{
scanf("%lf",yy+i);
yy[i]=log(yy[i]);
} while(cas--)
{
scanf("%d%d",&a,&b);
cap[a][b+n] += FINF;
cap[0][a] = xx[a];
cap[n+b][n+m+1] = yy[b];
} /*
for(int i=1; i<=n; i++)
{
scanf("%lf",xx+i);
xx[i]=log(xx[i]);
}
for(int i=1; i<=m; i++)
{
scanf("%lf",yy+i);
yy[i]=log(yy[i]);
}
while(cas--)
{
scanf("%d%d",&a,&b);
cap[a][b+n] += FINF;
cap[0][a] = xx[a]; ///没想到这样也对,但是在邻接表的时候就出问题了!!!
cap[n+b][n+m+1] = yy[b];
// printf("---a=%d,b=%d %f %f %f***",a,n+b,cap[a][a+b],cap[0][a],cap[a+b][m+n+a]);
}*/ EK(0);
}
return 0;
}
第二种用的链表:
#include <stdio.h>
#include <iostream>
#include <queue>
#include <cmath>
#include <vector>
#include <string.h>
#define maxn 110
#define INF 0x3f3f3f3f
#define FINF 99999999.0
using namespace std; struct Edge{
int from,to;
double cap,flow;
Edge(int u,int v,double c,double f):from(u),to(v),cap(c),flow(f){};
}; struct EK{
vector<Edge> edge;
vector<int> G[maxn];
int pre[maxn]; ///记录路径
double res[maxn] ; ///记录当前节点能通过的最大流量 void init(int n){
for(int i=0;i<n;i++) G[i].clear();
edge.clear();
}
void addedge(int from,int to,double cap ){
edge.push_back(Edge(from,to,cap,0));
edge.push_back(Edge(to,from,0,0));
int m=edge.size();
G[from].push_back(m-2); ///从G出去的边
G[to].push_back(m-1);
}
double maxflow(int s,int t){
double ans = 0;
while(true){
queue <int> que;
memset(res,0,sizeof(res));
res[s] = FINF;///源点的残留网络要置为无限大!否则下面找增广路出错
//pre[s] = -1;
que.push(s);
while(!que.empty()){ ///bfs找增广路径
int x = que.front();
que.pop();
for(int i=0;i<(int)G[x].size() ;i++){
Edge e = edge[ G[x][i] ] ;
if( !res[e.to] && e.cap > e.flow ){
que.push(e.to);
pre[e.to] = G[x][i];
res[e.to] = min(e.cap - e.flow , res[x]);///这里类似dp,如果有增广路,那么res[t]就是增广路的最小权
}
}
if(res[t]) break; ///找到一条增广路径
}
if(res[t]==0) break;///找不到增广路
for(int u=t;u != s; u= edge[ pre[u] ].from){
edge[ pre[u] ].flow += res[t];///正向边加上新的流量
edge[ pre[u]^1 ].flow -= res[t];///反向边要减去新的流量,反向边的作用是给程序一个后悔的机会
}
ans += res[t];
}
return ans;
}
};
int main(){
int T,cas,n,m,a,b;
double xx[55],yy[55];;
scanf("%d",&T);
while(T--){ scanf("%d%d%d",&n,&m,&cas);
EK ek;
ek.init(m+n+1); for(int i=1;i<=n;i++){
scanf("%lf",xx+i);
xx[i]=log(xx[i]);
ek.addedge(0,i,xx[i]);
}
for(int i=1;i<=m;i++){
scanf("%lf",yy+i);
yy[i]=log(yy[i]);
ek.addedge(n+i,n+m+1,yy[i]);
} while(cas--){
scanf("%d%d",&a,&b);
ek.addedge(a,b+n,FINF);
// printf("---a=%d,b=%d %f %f %f %f %f %f***",a,n+b,FINF,xx[a],yy[a],ek.edge[ek.edge.size()-6].cap,ek.edge[ek.edge.size()-4].cap,ek.edge[ek.edge.size()-2].cap);
}
printf("%.4f\n",exp( ek.maxflow(0,m+n+1) ) );
}
return 0;
}
POJ 3308 Paratroopers(最小割EK(邻接表&矩阵))的更多相关文章
- zoj 2874 & poj 3308 Paratroopers (最小割)
意甲冠军: 一m*n该网络的规模格.详细地点称为伞兵着陆(行和列). 现在,在一排(或列) 安装激光枪,激光枪可以杀死线(或塔)所有伞兵.在第一i安装一排 费用是Ri.在第i列安装的费用是Ci. 要安 ...
- POJ - 3308 Paratroopers (最小点权覆盖)
题意:N*M个格点,K个位置会有敌人.每行每列都有一门炮,能打掉这一行(列)上所有的敌人.每门炮都有其使用价值.总花费是所有使用炮的权值的乘积.求最小的总花费. 若每门炮的权值都是1,就是求最小点覆盖 ...
- POJ - 3308 Paratroopers(最大流)
1.这道题学了个单词,product 还有 乘积 的意思.. 题意就是在一个 m*n的矩阵中,放入L个敌军的伞兵,而我军要在伞兵落地的瞬间将其消灭.现在我军用一种激光枪组建一个防御系统,这种枪可以安装 ...
- POJ 3308 Paratroopers(最大流最小割の最小点权覆盖)
Description It is year 2500 A.D. and there is a terrible war between the forces of the Earth and the ...
- POJ 3308 Paratroopers(最小点权覆盖)(对数乘转加)
http://poj.org/problem?id=3308 r*c的地图 每一个大炮可以消灭一行一列的敌人 安装消灭第i行的大炮花费是ri 安装消灭第j行的大炮花费是ci 已知敌人坐标,同时消灭所有 ...
- POJ 3308 Paratroopers (对数转换+最小点权覆盖)
题意 敌人侵略r*c的地图.为了消灭敌人,可以在某一行或者某一列安置超级大炮.每一个大炮可以瞬间消灭这一行(或者列)的敌人.安装消灭第i行的大炮消费是ri.安装消灭第j行的大炮消费是ci现在有n个敌人 ...
- Paratroopers(最小割模型)
http://poj.org/problem?id=3308 题意:一个m*n的网格,有L位火星空降兵降落在网格中,地球卫士为了能同时消灭他们,在网格的行或列安装了一个枪支,每行或每列的枪支都能消灭这 ...
- POJ 2259 - Team Queue - [队列的邻接表]
题目链接:http://poj.org/problem?id=2259 Queues and Priority Queues are data structures which are known t ...
- poj 3084(最小割)
题目链接:http://poj.org/problem?id=3084 思路:题目的意思是不让入侵者进入保护的房间,至少需要锁几道门.网络流建模:设一个超级源点,源点与有入侵者的房间相连,边容量为in ...
随机推荐
- jsp中的内置对象(9个)、作用
jsp内置对象 定义:可以不加声明就在JSP页面脚本(Java程序片和Java表达式)中使用的成员变量 JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应): 1.request对象 客户 ...
- rm命令
rm是一个危险的命令,使用的时候要特别当心,尤其对于新手,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * -rf).所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西 ...
- javascript中的变量、执行环境、作用域
- 剑指offer系列43---判断平衡二叉树
[题目]判断一颗二叉树是不是平衡二叉树. * 平衡二叉树定义:任意子节点深度相差不超过1.[思路]由上题,利用递归得到二叉树每个结点的深度同时比较. package com.exe9.offer; i ...
- Linux From Scratch [2]
1. gcc需要的一些lib GMP:A free library for arbitrary precision arithmetic, operating on signed integers, ...
- Python 创建和发布安装函数模块
1. create dir "nester" under C:\Users\eric\AppData\Local\Programs\Python\Python35-32\ 2. c ...
- flash文件制作笔记
在uboot串口台输入printenv 可以分区以及其他信息,如下 hisilicon # printenv bootdelay=1baudrate=115200ethaddr=00:00:23:34 ...
- 黄聪:怎么清理win7、win8更新垃圾(winsxs目录清理)
windows 系统(特别是Win8系统)在使用了一段时间后,发现C盘的空间降的好厉害,显然,有大量不该存在的文件还继续停留在硬盘里.究其原因,在于系统目录下的WinSxS目录占用了大量的空间!在我们 ...
- Spring配置xml文件详解
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- c++学习-数组
int a[10]; //是个元素,在windows下回报错,linux会输出一个随机数 int a[10]={1,2}; //初始化,其他的为0 数组越界: 为了调高效率, 编译器不会对数组越界做检 ...