问题:二分图的最小点权覆盖集
思路:
1.构造二分图:分别把行、列的编号看成集合X={r1,r2...}、Y={c1,c2...},把每个人所在的位置(ri,ci)连线ri 、ci;这样就构成了一个二分图,点为行、列的顺序编号1~r+c,边则是人所在的位置(可以代表这个人)。
2.构造二分网络流图:分别添加源点S、汇点T,建立S->X的各个边,容量为该行ri的费用,反向为0;建立Y ->T的各个边,容量为该列ci的费用,反向为0;把所有X->Y的边的容量设为无穷大,反向为0。
3.求最大流,有时还需要求出最小割中的各个割边的容量(或者是对应的点ri or ci)。
注意:product在这里译为“乘积”,所以本题求的是各个cost的乘积的最小值,那么需转换成求最小和;建图
时流量为log(cost)即对各个cost求自然对数;再求出最大流maxflow,最后exp(maxflow)即e^maxflow;
a*b*c=e^(loga+logb+logc);还有精度的问题,要用double。
粗略理解:
为什么所求的最大流就是最小点权?
对二分图建立网络流图,他用了贪心的思想,对于X、Y中的点xi、yi:
若为1:1即只有一条边的情况,那么他选择的是S->x和y->T中较小的容量即选择了点权较小的点;
若为1:m即有多条边的情况,那么他选择的是S->x和y1->T+y2->T+...+yn->T中较小的容量;
若为m:1即有多条边的情况,那么他选择的是......较小的容量;
但是真实情况中,不像上边那么单纯,而是各种单纯的各种混合;但关键的一点不变:贪心的思想,选最小 的点权!更重要的是:对于每一种单纯情况的抉择后,不仅较小的点权的值更新为0,那么较大点权的也要 更新(减去较小的点权,这里所说的点权就是网络流图中的容量(S->x、y->T),为了便于说明)!这就保 证了不重选且最小!
概括地说:对于X,Y中的每一条边(x--y),若想覆盖掉这条边,则必须选择点x、y中之一才能覆盖之,别无他法 !所以肯定选择最小点权的点,并更新这两个点权的值——为了较大点权的点的再与别的点的选择时,必须建立在更新的基础上,不然就重选了!
为什么所求的各个点一定覆盖所有的边?
首先对于求完最大流的残留图,最小割的各个割边一定在S->X、S->Y中;如果你说从割边上选择的点不一定能覆盖所有点,也就是存在一条边e(x-y)的两个点x,y都不在割边上,那也就是说存在一个可行流S->x->y->T!而最大流是不存在可行流的!所以.....
#include <stdio.h>
#include <math.h>
#include <memory.h>
#define N 102
#define M 1202
#define MAXVAL (1e+6)//本题相当于e^10^6>2^10^6的数字啊
int r,c,m,s,t;
int nodevp[N];
int nodeu[M],next[M],ind;
double flow[M];
void addedge(int v,int u,double val)
{
nodeu[ind]=u;
flow[ind]=val;
next[ind]=nodevp[v];
nodevp[v]=ind++;
}
void getDataAndBuildGraph()
{
int i,v,u;
double cost;
scanf("%d %d %d",&r,&c,&m); s=0; t=r+c+1;
memset(nodevp,-1,sizeof(nodevp)); ind=0;
for(i=1;i<=r;i++)
{
scanf("%lf",&cost);
addedge(s,i,log(cost));
addedge(i,s,0.0);
}
for( ;i<t;i++)
{
scanf("%lf",&cost);
addedge(i,t,log(cost));
addedge(t,i,0.0);
}
for(i=0;i<m;i++)
{
scanf("%d %d",&v,&u);
addedge(v,u+r,MAXVAL);
addedge(u+r,v,0.0);
}
}
int dist[N],cur[N],pre[N],cnt[N];
double SAP()
{
int i,v,u;
double minflow,maxflow=0;
memset(cnt,0,sizeof(cnt));
memset(dist,0,sizeof(dist));
memcpy(cur,nodevp,sizeof(nodevp));
cnt[0]=t+1; v=s;
while(1)
{
for(i=cur[v]; ~i ; )
{
u=nodeu[i];
if(flow[i]>0 && dist[v]==dist[u]+1)
{
cur[v]=i; pre[u]=v;
if(minflow>flow[i]) minflow=flow[i];
if(u==t)
{
maxflow+=minflow;
while(u)
{
u=pre[u];
flow[cur[u]]-=minflow;
flow[cur[u]^1]+=minflow;
}
minflow=MAXVAL;
}
v=u; i=cur[v];
}
else i=next[i];
}
if(--cnt[dist[v]] == 0) break;
for(dist[v]=t,i=nodevp[v];~i;i=next[i])
{
u=nodeu[i];
//刚调试到这的时候,我一看就发现少了个条件flow[i]>0,但是我没停止就是想看看,到底是怎么错的...
// if(dist[v]>dist[u])//好吧,问题就出现在这里!!死循环!!调试用时30min+...
//好吧,既然已经浪费了很多时间,也不多这一会儿!来,咱们来看看是怎么回事
//刚开始没问题.....这时是:dist[S]=3,dist[3]=2,dist[7]=1,dist[T]=0; s->3->7->T存在一条增广路!
//更新完流量后,7->T之间的流量变为0;然后又回到S,仍从S->3这条线找,因为cur[S]记录了S->3边;
//从3->7,7无路可走,只好重新标号!注意错误就出现在这里:标完号,7的标号仍是1!很显然应该是3,因为
//7->T走不通!这里没有了这个flow[i]>0的判断,直接把dist[7]又变回了1;然后回到3,3->7,7无路可走,只好
//重新标号,标完号dist[7]仍是1;然后又回到了3.......就这样死循环!!!!
//啊! 累死哥们了!这尼玛伤不起,为找这个死循环,费了巨大的脑力(人脑没法跟电脑比!),1h+的时间!!
if(flow[i]>0 && dist[v]>dist[u])
dist[v]=dist[u],cur[v]=i;
}
dist[v]++; cnt[dist[v]]++;
if(v==s) { if(dist[s]>t) break; minflow=MAXVAL; }
else v=pre[v];
}
return maxflow;
}
//int flag[N];
//void DFS(int v)
//{
// int i;
// flag[v]=1;
// for(i=nodevp[v];~i;i=next[i])
// if(flow[i]>0 && !flag[nodeu[i]])
// DFS(nodeu[i]);
//}
void solve()
{
int cas;
// double ans;
for(scanf("%d",&cas); cas ; cas--)
{
getDataAndBuildGraph();
printf("%.4lf\n",exp(SAP()));
// printf("%.4f\n",SAP());
// memset(flag,0,sizeof(flag));
// DFS(s);
// ans=1.0;
// for(i=1;i<=r;i++)
// if(!flag[i]) ans*=cost[i];
// for( ;i< t;i++)
// if( flag[i]) ans*=cost[i];
// printf("%.4lf\n",ans);
}
}
int main()
{
freopen("input.txt","r",stdin);
solve();
return 0;
}
心理历程:
这尼玛坑死爹了啊!读题的时候都开始蛋疼!
就是这句:the total cost of constructing a system firing all guns simultaneously is equal to the product of their costs. 我日尼玛啊!坑死爷们了啊!当时怎么读就是觉得不顺!如果翻译成:求最小的消费,那么他为什么要product of their costs呢?怎么都不顺!他们消费的产品?这尼玛啥意思啊?!考!!郁闷!不管了!就当是求最小消费!可是样例输出怎么与我求的对不住?按说是8.000啊,怎么会是16.0000!晕死!难不成是求最大的、那不可能!难不成是cost*行号或列号、那也不对!难道是我行列弄反了、反过来还是8.000啊 !这。。。可能还是那句话没翻译好?我又去翻译那句话。。。就是尼玛看不懂product of their costs!!就 这反复、轮回的去想。。期间我又找人帮我翻译。。等等。。上午结束了。。。没有结果。。。下午继续搞。。8.0000*2=16.0000这个对吧,难道是要求最大流*2。好吧,我就这样做吧,比照着二分图的最小点权覆盖集的模型去建图(因为我就是搜得这个专题)。。那这题。。那不是比那个2125简单啊,不用求最小割的各个割边容量(或者是点)呵,那这建完图整个SAP不就可以了。。好的~想想……敲代码!!敲完,测试,结果错了,看了几遍代码看不出来,无奈很不想调试的去调试了,很快找到了SAP中某地方少了一个条件,代码中有详解,可是我不甘心,想看看它怎么是程序失败。。继续调,调了半个多钟头、终于发现就是少一个条件而造成死循环。啊,累死了,整个过程足有1个小时。。在测试,成功!(真SB,printf("%d",SAP()*2);真尼玛搞笑),好吧submit,果断WA,在提交,依然WA!。。。我日你嘛啊,哥哥实在是木法了,Only search answer!有了一个惊天的发现:product翻译成“乘积”,我考,这、、、还有这个意思?从未听说!!好吧,求乘积!那。。。求最大流,求的是最小和啊!那我求出,最小割中的各个容量的乘积?那不对吧?若a+b+c最小,a*b*c就最小?这不对!那。。。有啥法啊?这尼玛让求乘积最小。。这木法。。这确实木法!Again look answer 发现是:先求各个cost的log,在对求出的和(最大流)exp运算!这尼玛什么情况 ?我想了想。。a*b*c=e^(loga+logb+logc) 。。(⊙o⊙)…对啊,我去,这谁尼玛真牛B呀,求乘积的最小值转化成求log和的最小值!额,也许大家都经常用呵,你懂的,我很水嘛!考,最终在源代码上,建图的时候加了log,在SAP外加个exp!我表示这两个函数貌似是我第一次用!!O(∩_∩)O哈哈~,我很无敌吧!!额,此时的代码已改的面目全非!蛋液碎了一地!