网络流第24题:数字梯形问题解析
本文章同步发表于我的博客
简述题意:
题目中给了你一个\(n\)行,第\(i\)行有\(m+i-1\)个数字,有三种询问:
询问一:让你选择一条路径,是的每个路径不经过重复的点,重复的边,然后经过的点的数字和最大
询问二:在询问一的基础上允许经过重复点
询问三:在询问二的基础上还允许经过重复边
思路分析:
\(subtask1:\)
询问一让你求出\(m\)条不重复路径,然后经过的点权和最大,首先找m条不重复路径这里,我这个蒟蒻开始感觉与最小路径覆盖问题有关,后来发现不对,因为你又走不完所有点,所以不要往那上面去想。
建模分析:
首先我们发现这道题的权为点权。
\([1]\)那么肯定考虑拆点,拆分为入点和出点,让点权变化为边权,然后由入点向出点连边,连一条流量为\(1\),费用为点权的边。建这条边建流量为\(1\)表示这条边只能被经过一次,也即:这个点只能被经过一次,费用就是统计点权。然后,每个点只能经过一次的条件就满足了。
\([2]\)因为初始第一排\(m\)个点都得经过一次,所以,超级源点\(s\)向每个点连一条流量为\(1\),费用为\(0\)的边,表示只能经过一次,费用为\(0\),则不会计算进最终答案。
\([3]\)同理,每个最后一排的点,都有可能被经过一次,注意,这里是可能\((\)当然对做题没影响就对了\(qAQ)\)。所以每个最后一排的点的出点向超级汇点\(t\)连一条边,流量为\(1\),费用为\(0\),理由同上。
\([4]\)接下来就是处理每个点之间的关系,我们发现一个点\((i,j)\)可以往他的下方\((i+1,j)\)和右下方\((i+1,j+1)\)走,也就是这个点和这两个点中间可以连边。但是注意这里的连边是要由出点向入点连。
为什么?开篇提到了不要把这道题与最小路径覆盖连接起来,两道题不一样\((\)虽然可能只有我这个\(SB\)蒟蒻是连接起来的\()\)。最小路径覆盖,你由入点向出点连,是因为你首先你有一个定理:最小路径覆盖数=总点数-二分图最大匹配数 。
这是让你求的是你的路径里的边数,所以跑一次就统计一次答案。但这里不一样,你要统计的是它的费用,你如果直接由出点向入点连边,连完了你就会跑出去,然后你还默认跑了一条路径,这样的答案就不对。
所以你得从出点向入点连边,这样保证你得路径能走完,且统计到答案。然后,由于你每次是向下方的两个点连边,其他点不连边,所以是会保证一定从顶部到底部的。
然后跑一遍最大费用最大流,输出答案即可。
唔,建图应该是这样的\(QAQ\):

代码如下:
///处理情况1
void subtask1(){
for(int i=1;i<=m;i++) add(s,spot[1][i],1,0),add(spot[1][i],s,0,0);///s和第一排的m个点连边
for(int i=1;i<=len[n];i++) add(spot[n][i]+cnt,t,1,0),add(t,spot[n][i]+cnt,0,0);///最后一排的点向t连边
for(int i=1;i<=n;i++){
for(int j=1;j<=len[i];j++){
add(spot[i][j],spot[i][j]+cnt,1,line[i][j]);
add(spot[i][j]+cnt,spot[i][j],0,-line[i][j]);///入点向出点连边
if(i==n) continue;
add(spot[i][j]+cnt,spot[i+1][j],1,0);
add(spot[i+1][j],spot[i][j]+cnt,0,0);///上下关系连边
add(spot[i][j]+cnt,spot[i+1][j+1],1,0);
add(spot[i+1][j+1],spot[i][j]+cnt,0,0);///上下关系连边
}
}
EK(s,t);
printf("%lld\n",mincost);
}
...
signed main(){
read(m),read(n);///先读入m后读入n
now=m;
for(int i=1;i<=n;i++){
len[i]=now;/// 统计每一排可能会有多少个数
for(int j=1;j<=now;j++){
read(line[i][j]); ///读入点的点权
spot[i][j]=++cnt; ///记录这个点是第几个点,总共有几个点
}
now++;
}
}
\(subtask2:\)
这里只要你把上面的\(subtask1\)的思路弄懂了,那这边就很简单了,这里可以经过重复点。那就把最后一排与\(t\)的流量设为\(INF\),然后入点和出点之间连边的流量设为\(INF\),同样跑一边最大费用最大流即可$qwq \ $。
\(code:\)
void subtask2(){
num=1;
memset(first,0,sizeof(first));
memset(v,0,sizeof(v));
memset(flow,0,sizeof(flow));
memset(cost,0,sizeof(cost));
memset(nex,0,sizeof(nex));
for(int i=1;i<=m;i++) add(s,spot[1][i],1,0),add(spot[1][i],s,0,0);///s和第一排的m个点连边
for(int i=1;i<=len[n];i++) add(spot[n][i]+cnt,t,INF,0),add(t,spot[n][i]+cnt,0,0);///最后一排的点向t连边
for(int i=1;i<=n;i++){
for(int j=1;j<=len[i];j++){
add(spot[i][j],spot[i][j]+cnt,INF,line[i][j]);
add(spot[i][j]+cnt,spot[i][j],0,-line[i][j]);///入点向出点连边
if(i==n) continue;
add(spot[i][j]+cnt,spot[i+1][j],1,0);
add(spot[i+1][j],spot[i][j]+cnt,0,0);///上下关系连边
add(spot[i][j]+cnt,spot[i+1][j+1],1,0);
add(spot[i+1][j+1],spot[i][j]+cnt,0,0);///上下关系连边
}
}
mincost=0;
EK(s,t);
printf("%lld\n",mincost);
}
\(subtask3:\)
这里没有其他的限制了,哇噻,那就很开心了,除了第一排\(m\)个点与源点\(s\)的边,其他流量均设为\(INF\),那就完事了\(QWQ\)。
\(code:\)
void subtask3(){
num=1;
memset(first,0,sizeof(first));
memset(v,0,sizeof(v));
memset(flow,0,sizeof(flow));
memset(cost,0,sizeof(cost));
memset(nex,0,sizeof(nex));
for(int i=1;i<=m;i++) add(s,spot[1][i],1,0),add(spot[1][i],s,0,0);///s和第一排的m个点连边
for(int i=1;i<=len[n];i++) add(spot[n][i]+cnt,t,INF,0),add(t,spot[n][i]+cnt,0,0);///最后一排的点向t连边
for(int i=1;i<=n;i++){
for(int j=1;j<=len[i];j++){
add(spot[i][j],spot[i][j]+cnt,INF,line[i][j]);
add(spot[i][j]+cnt,spot[i][j],0,-line[i][j]);///入点向出点连边
if(i==n) continue;
add(spot[i][j]+cnt,spot[i+1][j],INF,0);
add(spot[i+1][j],spot[i][j]+cnt,0,0);///上下关系连边
add(spot[i][j]+cnt,spot[i+1][j+1],INF,0);
add(spot[i+1][j+1],spot[i][j]+cnt,0,0);///上下关系连边
}
}
mincost=0;
EK(s,t);
printf("%lld\n",mincost);
}
题解过水,请别骂,因为我也发现自己好傻,其实这个好简单的。

浙公网安备 33010602011771号