HOJ(哈工大) 2816
//题目类型:最大流+二分搜索
//本题的关键在于线的长度一样长(使用二分搜索实现)
#include<iostream>
#include<queue>
#include<algorithm>
//#include<conio.h>
using namespace std;
#define narray 440
#define INF 1000000
int flow; //最大流
int s,t; //源点,汇点
int dis[narray][narray]; //距离
int dissort[narray*narray];
int c[narray];
int n,m,ncase,p;
int maxLen;
int capacity[narray][narray];
int pre[narray];
int num[narray];
int d[narray];
int vn;
void init(int src,int des)
{
int i,j;
queue<int> myqueue;
myqueue.push(des);
memset(num,0,sizeof(num));
for(i=0;i<vn;++i)
d[i] = INF;
d[des] = 0;
num[0] = 1;
int frontint;
while(!myqueue.empty())
{
frontint = myqueue.front();myqueue.pop();
for(i=0;i<vn;++i)
{
if(d[i]>=vn && capacity[i][frontint]>0)
{
d[i] = d[frontint]+1;
myqueue.push(i);
num[d[i]]++;
}
}
}
}
int findarc(int t)
{
int i,j;
for(i=0;i<vn;++i)
{
if(capacity[t][i]>0 && d[t]==d[i]+1) return i;
}
return -1;
}
int relabel(int t)
{
int i,j;
int mm = INF;
for(i=0;i<vn;++i)
{
if(capacity[t][i]>0) mm = min(mm,d[i]+1);
}
return mm==INF?vn:mm;
}
int maxFlow(int src,int des)
{
int sumflow = 0;
int delta;
int i=src;
int j;
memset(pre,-1,sizeof(pre));
while(d[src]<vn)
{
j = findarc(i);
if(j>=0)
{
pre[j] = i;
i = j;
if(i==des)
{
delta = INF;
for (i=des;i!=src;i=pre[i]) delta=min(delta,capacity[pre[i]][i]);
for (i=des;i!=src;i=pre[i]) capacity[pre[i]][i] -= delta, capacity[i][pre[i]] += delta;
sumflow += delta;
}
}
else
{
int x = relabel(i);
num[x]++;
num[d[i]]--;
if(num[d[i]]==0) return sumflow;
d[i] = x;
if(i!=src) i =pre[i];
}
}
return sumflow;
}
int build()
{
int i,j,k;
for(i=1;i<=n;i++) //源点到电脑之间的流量
capacity[s][i]=1;
for(i=n+1;i<=n+m+1;i++) //源点到其他点的流量
capacity[s][i]=0;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++) //电脑到电脑之间的流量
capacity[i][j]=0;
for(j=n+1;j<=n+m;j++) //电脑和电源点之间构图(此处是关键)
if(dis[i][j-n]<=maxLen) //如果电脑和电源点之间的距离小于给定的距离,则建立一条边
capacity[i][j]=1;
else
capacity[i][j]=0;
capacity[i][t]=0; //电脑到汇点之间构图
}
for(i=n+1;i<=n+m;i++)
{
for(j=0;j<=n+m;j++)
capacity[i][j]=0;
capacity[i][t]=c[i-n]; //电源点到汇点构图(据电源点的容量)
}
flow=0;
init(s,t);
flow = maxFlow(s,t);
return flow;
}
int main()
{
//freopen("1.txt","r",stdin);
int i,j,k;
unsigned long start;
scanf("%d",&ncase);
while(ncase--)
{
int discnt=0;
scanf("%d%d%d",&n,&m,&p);
vn = n+m+2; //结点总数
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
scanf("%d",&dis[i][j]);
dissort[discnt++]=dis[i][j];
}
for(i=1;i<=m;i++)
scanf("%d",&c[i]);
s=0;t=n+m+1; //定义超级源点和超级汇点
sort(dissort,dissort+discnt); //对所有的距离(相当于费用)进行升序排序(默认是升序),排序范围是[first, last)
int l=0,r=discnt-1,mid; //下标从0开始
while(l+1<r) //二分法求解
{
mid=(l+r)/2;
maxLen=dissort[mid];
int f=build(); //f为最大流
if(f==n) //如果流量等于最大值(缩小费用)
r=mid;
else
l=mid; //否则增大费用
}
if(discnt==1) printf("%d\n",dissort[0]*n*p); //考虑只有一台电脑一个电源的情况,此时l==r,直接输出结果
else if(l+1==r)
{
maxLen=dissort[l];
int f=build();
if(f==n)
printf("%d\n",maxLen*n*p);
else
{
maxLen=dissort[r];
f=build();
if(f==n)
printf("%d\n",maxLen*n*p);
else
printf("-1\n");
}
}
}
//getch();
return 0;
}