poj 3308 Paratroopers

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;
}


你可能感兴趣的:(dinic,二分图,最小点权覆盖)