http://poj.org/problem?id=3308
题意:
大意是在一个n*m的矩阵中分布着一些点,可以通过一定费用消灭某行或某列的点,求消灭所有点所需的最小总费用,总费用为单次费用之积。
思路:
因为总费用是单次费用之积,可以利用对数的性质,log(xy) = logx + logy将问题由乘法变成加法,先对原题的目标代价求对数。
构建模型:以行和列看做点集,有敌人的行列相连。令X = {行集合},Y={列集合},E= {log(eij) | 火星伞兵将空投在第i行第j列 };这样就构造一个带权二分图G= {X,Y, E}。本题是二分图中的一个顶点覆盖问题。
求二分图的顶点覆盖问题,通常转化为最小割来求。即建立源点S 和汇点 T,假设二分图两个点集分别为 X 和 Y。X和Y原来的边容量设为INF,将S到X里每个点x连一条边,边权为x的点权,也可以看做覆盖点x的所有出边的花费(W-),将Y里每个点y到T连一条边,边权为y的点权,也可以看做覆盖点y的所有入边的花费(W+)。这样求得最小割容量,即为原问题的解。
而图G的最小割的容量,等于其最大流的流量,因此本题最终是转化为最大流问题求解。Dinic算法,特别注意加边的时候加反向边。
#include <stdio.h> #include <string.h> #include <algorithm> #include <queue> #include <cmath> using namespace std; const int maxm = 5050; const int maxn = 5050; const int INF = 0x3f3f; struct node { int u,v; double w; int next; }edge[maxm]; int cnt,head[maxm]; int s,t; int n,m,l; void add(int u, int v, double w) { edge[cnt].u = u; edge[cnt].v = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt; cnt++; edge[cnt].u = v; edge[cnt].v = u; edge[cnt].w = 0; edge[cnt].next = head[v]; head[v] = cnt; cnt++; } int vis[maxn],dist[maxn]; queue <int> que; void bfs() { memset(dist,0,sizeof(dist)); while(!que.empty()) que.pop(); vis[s] = 1; que.push(s); while(!que.empty()) { int u = que.front(); que.pop(); for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(edge[i].w && !vis[v]) { que.push(v); vis[v] = 1; dist[v] = dist[u]+1; } } } } double dfs(int u, double delta) { if(u == t) return delta; else { double ret = 0; for(int i = head[u]; i != -1 && delta; i = edge[i].next) { if(edge[i].w && dist[edge[i].v] == dist[u]+1) { double dd = dfs(edge[i].v, min(delta,edge[i].w)); edge[i].w -= dd; edge[i^1].w += dd; delta -= dd; ret += dd; } } return ret; } } double Dinic() { double ret = 0; while(true) { memset(vis,0,sizeof(vis)); bfs(); if(!vis[t]) return ret; ret += dfs(s,INF); } } int main() { int test; double w; int x,y; scanf("%d",&test); while(test--) { scanf("%d %d %d",&n,&m,&l); s = 0; //超级源点 t = n+m+1; //超级汇点 memset(head,-1,sizeof(head)); cnt = 0; for(int i = 1; i <= n; i++) { scanf("%lf",&w); add(s,i,log(w)); //从S到X集合,先取对数建边 } for(int i = 1+n; i <= m+n; i++) { scanf("%lf",&w); add(i,t,log(w)); //从Y到T集合,先取对数建边 } for(int i = 0; i < l; i++) { scanf("%d %d",&x,&y); add(x,y+n,INF); //X到Y建边,流量INF } double ans; ans = Dinic(); printf("%.4f\n",exp(ans)); } return 0; }