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 ...
随机推荐
- html之hr,form标签
<hr>标签:在html页面中创建一条水平线,可在视觉上将文档分隔成多个部分 <form>:块级标签,前后会产生折行 标签用于为用户输入创建html表单,将数据提交给服务器.表 ...
- javascript 中根据sort 方法随机数组 (Math.random)
var arr = [1,2,3,4,5,6,7,8,9,10]; function Arandom(a,b){ return (Math.random() > 0.5) ? 1 : -1;; ...
- windows p12(pfx)个人证书安装过程
证书库个人证书存储区为其中的每个证书维护一个属性CERT_KEY_PROV_INFO_PROP_ID,该属性指定了证书对应的密钥容器的相关信息,包括密钥容器名,CSP名称,CSP类型,密钥用途,以及C ...
- 【linux】which和whereis
which和whereis都是查询命令的指令.区别的是: which能查询到命令所在位置: [root@andon tmp]# which ls alias ls='ls --color=auto' ...
- get utc+8 当时时间
/// <summary> /// get utc+8 当时时间 /// </summary> /// <returns></returns> publ ...
- 剑指offer系列62---两个链表的公共结点
[题目]输入两个链表,找出它们的第一个公共结点. * [思路]1 获取两链表的长度: * 2 让长的链表先走n步后此时走到短链表起始位置: * 3 两链表同时遍历,直至相同,这时返回第一个公共结点. ...
- 【Log4j】 log4j.properties 使用
一.参数意义说明 输出级别的种类 ERROR.WARN.INFO.DEBUG ERROR 为严重错误 主要是程序的错误 WARN 为一般警告,比如session丢失 INFO 为一般要显示的信息,比如 ...
- php 查询出来的字段名全是小写或者大写
PHP PDO预定义常量 PDO::CASE_LOWER -- 强制列名是小写PDO::CASE_NATURAL -- 列名按照原始的方式PDO::CASE_UPPER -- 强制列名为大写 修改此参 ...
- linux下查看进程运行的时间
原文链接:http://www.centoscn.com/CentOS/2014/0403/2724.html 可通过ps 来查看,通过参数 -o 来查看 例: ps -eo pid,tty,user ...
- Makefile编译选项CC与CXX/CPPFLAGS、CFLAGS与CXXFLAGS/LDFLAGS
转自:http://www.firekyrin.com/archives/597.html 编译选项 让我们先看看 Makefile 规则中的编译命令通常是怎么写的. 大多数软件包遵守如下约定俗成的规 ...