网络流第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);
}

题解过水,请别骂,因为我也发现自己好傻,其实这个好简单的。

posted @ 2021-02-10 10:31  edds  阅读(166)  评论(0)    收藏  举报