首页 > 其他分享 >Luogu P4013 数字梯形问题

Luogu P4013 数字梯形问题

时间:2023-01-15 10:23:25浏览次数:56  
标签:cnt 数字 int Luogu 梯形 edge P4013 INF head

说实话这道题挺乐的,去年11月学网络流时碰到这道题,一直没想通,结果碰到考试月,被遣返回家,一个多月没摸了,今天看到这道题一下子想通了,于是想记下来。

题目传送门

P4013 数字梯形问题

题意

给定一个由 n 行数字组成的数字梯形如下图所示。

image

梯形的第一行有 m 个数字。从梯形的顶部的 m 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。

分别遵守以下规则:

  1. 从梯形的顶至底的 m 条路径互不相交;
  2. 从梯形的顶至底的 m 条路径仅在数字结点处相交;
  3. 从梯形的顶至底的 m 条路径允许在数字结点相交或边相交。

将按照规则 1,规则 2,和规则 3 计算出的最大数字总和并输出,每行一个最大总和。

思路

很显然,这道题的思路是最大费用最大流。但三个规则中,我认为2、3是比较简单的,而规则1建图略微复杂一点。我们先从规则3讲起。

规则3

从梯形的顶至底的 m 条路径允许在数字结点相交或边相交,也可以说是没有任何限制了。

我们按以下思路建图:

  1. 源点到第一行各点建容量为1,费用为0的边;
  2. 除最后一行的每一个点向下一行左下、右下两个点分别建容量为INF,费用为负的该点数字的边;
  3. 最后一行的每一个点向汇点建容量为INF,费用为为负的该点数字的边。

按样例建图如下图所示。

image

图中S为源点,T为汇点。

按上述建边方法,源点向第一行两个点分别建边(1,0);第一行第一个点向第二行第一、二个点分别建边(INF,-2);第一行第二个点分别向第二行第二、三个点分别建边(INF,-3)……;最后一行各点向汇点建边(INF,-该点数字)。建完边后跑费用流,负的费用即为答案。

规则2

从梯形的顶至底的 m 条路径仅在数字结点处相交,相比第3条规则,路径两两不能共用一条边,即每条边允许容量为1。

稍微改一下规则3,我们就可以得到下面建图方法:

  1. 源点到第一行各点建容量为1,费用为0的边;
  2. 除最后一行的每一个点向下一行左下、右下两个点分别建容量为1,费用为负的该点数字的边;
  3. 最后一行的每一个点向汇点建容量为INF,费用为为负的该点数字的边。注意这里建边容量为INF,因为最底层向汇点不用限制流量。

按样例建图如下图所示。

image

按此方法建图即可保证这些路径不会在边处重合。建图完后依旧跑费用流取负费用即为答案。

规则1

从梯形的顶至底的 m 条路径互不相交,相较于第2条规则,各路径也不能在点处相交,这意味着我们需要在规则2的基础上,对点限流。

对点限流的方法即为拆点,将一个点拆为两个点,分别为“入点”和“出点”,所有连向该点的边都应连向该点的入点,该点的所有出边都应从出点连出;每个点的入点还需要向该点的出点连一条容量为1,费用为0的边,即可限制每一点的流量。

我们可以得到如下建图方法:

  1. 源点到第一行各点的入点建容量为1,费用为0的边;
  2. 除最后一行的每一个点的出点向下一行左下、右下两个点的入点分别建容量为1,费用为负的该点数字的边;
  3. 最后一行的每一个点的出点向汇点建容量为INF,费用为为负的该点数字的边。注意这里建边容量为INF,因为最底层向汇点不用限制流量;
  4. 每个点的入点出点建一条容量为1,费用为0的边。

代码

规则3

void ans3() {
    cnt = 0;//初始化图
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            if (i == 1)add(S, get(i, j, 0), 1, 0);//源点到第一行各点建容量为1,费用为0的边
            if (i == n)add(get(i, j, 0), T, INF, -num[i][j]);//最后一行的每一个点向汇点建容量为INF,费用为为负的该点数字的边
            else {
                //除最后一行的每一个点向下一行左下、右下两个点分别建容量为INF,费用为负的该点数字的边
                add(get(i, j, 0), get(i + 1, j, 0), INF, -num[i][j]);
                add(get(i, j, 0), get(i + 1, j + 1, 0), INF, -num[i][j]);
            }
        }
    }
    int flow, cost;
    EK(flow, cost);//跑EK费用流
    cout << -cost << endl;//输出负费用
}

规则2

void ans2() {
    cnt = 0;//初始化图
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            if (i == 1)add(S, get(i, j, 0), 1, 0);//源点到第一行各点建容量为1,费用为0的边
            if (i == n)add(get(i, j, 0), T, INF, -num[i][j]);//最后一行的每一个点向汇点建容量为INF,费用为为负的该点数字的边。注意这里建边容量为INF,因为最底层向汇点不用限制流量
            else {
                //除最后一行的每一个点向下一行左下、右下两个点分别建容量为1,费用为负的该点数字的边
                add(get(i, j, 0), get(i + 1, j, 0), 1, -num[i][j]);
                add(get(i, j, 0), get(i + 1, j + 1, 0), 1, -num[i][j]);
            }
        }
    }
    int flow, cost;
    EK(flow, cost);//跑EK费用流
    cout << -cost << endl;//输出负费用
}

规则1

void ans1() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            if (i == 1)add(S, get(i, j, 0), 1, 0);//源点到第一行各点的**入点**建容量为1,费用为0的边
            if (i == n)add(get(i, j, 1), T, 1, -num[i][j]);//最后一行的每一个点的**出点**向汇点建容量为INF,费用为为负的该点数字的边。注意这里建边容量为INF,因为最底层向汇点不用限制流量
            else {
                //除最后一行的每一个点的**出点**向下一行左下、右下两个点的**入点**分别建容量为1,费用为负的该点数字的边
                add(get(i, j, 1), get(i + 1, j, 0), 1, -num[i][j]);
                add(get(i, j, 1), get(i + 1, j + 1, 0), 1, -num[i][j]);
            }
            //每个点的**入点**向**出点**建一条容量为1,费用为0的边
            add(get(i, j, 0), get(i, j, 1), 1, 0);
        }
    }

    int flow, cost;
    EK(flow, cost);//跑EK费用流
    cout << -cost << endl;//输出负费用
}

完整代码

点击查看代码
/*
 * ycccc319
 */
#include <bits/stdc++.h>

using namespace std;
const int N = 2010, M = 100100, INF = 0x3f3f3f3f;

class node {
public:
    int v, f, w, next;
} edge[M];

int n, m, S, T;
int cnt = 0, head[N];
int d[N], pre[N], q[N], incf[N];
bool vis[N];

void add(int u, int v, int c, int w) {
    edge[cnt].v = v, edge[cnt].f = c, edge[cnt].w = w, edge[cnt].next = head[u], head[u] = cnt++;
    edge[cnt].v = u, edge[cnt].f = 0, edge[cnt].w = -w, edge[cnt].next = head[v], head[v] = cnt++;
}

bool spfa() {
    int hh = 0, tt = 1;
    memset(d, INF, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = INF;
    while (hh != tt) {
        int now = q[hh++];
        if (hh == N)hh = 0;
        vis[now] = 0;
        for (int i = head[now]; ~i; i = edge[i].next) {
            int to = edge[i].v;
            if (edge[i].f && d[to] > d[now] + edge[i].w) {
                d[to] = d[now] + edge[i].w;
                pre[to] = i;
                incf[to] = min(edge[i].f, incf[now]);
                if (!vis[to]) {
                    q[tt++] = to;
                    if (tt == N)tt = 0;
                    vis[to] = 1;
                }
            }
        }
    }
    return incf[T] > 0;
}

void EK(int &flow, int &cost) {
    flow = cost = 0;
    while (spfa()) {
        int t = incf[T];
        flow += t, cost += t * d[T];
        for (int i = T; i != S; i = edge[pre[i] ^ 1].v) {
            edge[pre[i]].f -= t;
            edge[pre[i] ^ 1].f += t;
        }
    }
    return;
}

int num[22][42];

int get(int x, int y, int c) {//在规则1中,c为1时是出点,为0时是入点,规则2、3里不适用
    return x * 40 + y + c * 1000;//这里为了省事直接对出点+1000了
}

void ans1() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            if (i == 1)add(S, get(i, j, 0), 1, 0);//源点到第一行各点的**入点**建容量为1,费用为0的边
            if (i == n)
                add(get(i, j, 1), T, 1,
                    -num[i][j]);//最后一行的每一个点的**出点**向汇点建容量为INF,费用为为负的该点数字的边。注意这里建边容量为INF,因为最底层向汇点不用限制流量
            else {
                //除最后一行的每一个点的**出点**向下一行左下、右下两个点的**入点**分别建容量为1,费用为负的该点数字的边
                add(get(i, j, 1), get(i + 1, j, 0), 1, -num[i][j]);
                add(get(i, j, 1), get(i + 1, j + 1, 0), 1, -num[i][j]);
            }
            //每个点的**入点**向**出点**建一条容量为1,费用为0的边
            add(get(i, j, 0), get(i, j, 1), 1, 0);
        }
    }

    int flow, cost;
    EK(flow, cost);//跑EK费用流
    cout << -cost << endl;//输出负费用
}

void ans2() {
    cnt = 0;//初始化图
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            if (i == 1)add(S, get(i, j, 0), 1, 0);//源点到第一行各点建容量为1,费用为0的边
            if (i == n)
                add(get(i, j, 0), T, INF, -num[i][j]);//最后一行的每一个点向汇点建容量为INF,费用为为负的该点数字的边。注意这里建边容量为INF,因为最底层向汇点不用限制流量
            else {
                //除最后一行的每一个点向下一行左下、右下两个点分别建容量为1,费用为负的该点数字的边
                add(get(i, j, 0), get(i + 1, j, 0), 1, -num[i][j]);
                add(get(i, j, 0), get(i + 1, j + 1, 0), 1, -num[i][j]);
            }
        }
    }
    int flow, cost;
    EK(flow, cost);//跑EK费用流
    cout << -cost << endl;//输出负费用
}

void ans3() {
    cnt = 0;//初始化图
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            if (i == 1)add(S, get(i, j, 0), 1, 0);//源点到第一行各点建容量为1,费用为0的边
            if (i == n)add(get(i, j, 0), T, INF, -num[i][j]);//最后一行的每一个点向汇点建容量为INF,费用为为负的该点数字的边
            else {
                //除最后一行的每一个点向下一行左下、右下两个点分别建容量为INF,费用为负的该点数字的边
                add(get(i, j, 0), get(i + 1, j, 0), INF, -num[i][j]);
                add(get(i, j, 0), get(i + 1, j + 1, 0), INF, -num[i][j]);
            }
        }
    }
    int flow, cost;
    EK(flow, cost);//跑EK费用流
    cout << -cost << endl;//输出负费用
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> m >> n;
    S = 0, T = 1;
    memset(head, -1, sizeof head);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m + i; j++) {
            cin >> num[i][j];
        }
    }
    ans1();
    ans2();
    ans3();
    return 0;
}

标签:cnt,数字,int,Luogu,梯形,edge,P4013,INF,head
From: https://www.cnblogs.com/ycccc319/p/17051537.html

相关文章

  • Luogu7509 撕裂消除 - 期望dp -
    题目链接:https://www.luogu.com.cn/problem/P7509题解:设\(dp[i][j][0/1]\)表示考虑到第\(i\)个位置,已经形成了极大的\(j\)段,当前位置为0/1的期望值;\(g[i][j][0......
  • 【luogu CF1707D】Partial Virtual Trees(容斥)(DP)
    PartialVirtualTrees题目链接:luoguCF1707D题目大意给你一棵以1为根的数,问你对于每个长度,有多少个点集序列,第一个点集是全部点,最后一个点集只有1号点,且中间每个点......
  • luogu P3518 [POI2011]SEJ-Strongbox | loj #2160. 「POI2011 R2 Day0」保险箱 Strong
    代码已在loj上不开O2通过。下文均在\(Z_n\)下考虑。首先,你考虑选出一些数,能组成的数。即ttps://www.cnblogs.com/xugangfan/p/17040634.html那么对于一个不在群......
  • Luogu P5465 [PKUSC2018] 星际穿越
    观察可以发现一个结论,可以视作每个点\(i\)可以一步到达\(l_i\simn\)的每一个点。发现对于\(a<b<x\),\(dist(a,x)\gedist(b,x)\)第一步是相当特殊的,因为第一步......
  • luogu P5291 [十二省联考 2019] 希望
    题面传送门真的很想吐。题目的意思大概就是在一棵树上选出\(k\)个联通块,使得这\(k\)个联通块有交。显然联通块的交还是联通块,因此转化为对联通块计数。而联通块个数等于......
  • [luogu]P2123 皇后游戏
    题目传送门分析和国王游戏一样的思路直接考虑邻项交换观察易知排在后面的大臣获得的奖赏一定更多假设前\(i-1\)位左手上的数和为\(a\),第\(i-1\)位获得奖赏为\(......
  • luogu P1971 [NOI2011] 兔兔与蛋蛋游戏
    题面传送门没想到二分图博弈这么早就考了?首先我们发现,如果将和起点行列之和奇偶性一样的点设为黑色,其余设为白色,那么每次空格只会在异色边之间走,而不会在同色边之间走。......
  • Luogu P4592 [TJOI2018]异或 做题记录
    随机跳的。树上维护序列,显然树剖。维护异或,显然01trie。01trie维护区间异或,显然可持久化一下。看到时限很大,显然可以双log。于是跑一边树剖,再根据id暴力建一个可......
  • luogu P5037 抓捕
    ##题意有$n$个点的图,任意一点$x$到可去另外一点$y$必须互质,即$\gcd(x,y)=1$。图无边权,但是拥有点权。求到终点$en$的最短距离。---##思路此题需使用Dijks......
  • Luogu P4591 [TJOI2018] 碱基序列
    链接难度:\(\texttt{省选/NOI-}\)有\(k\)组,每一组有\(a_i\)个字符串\(t_{k,a_i}\),询问分别在每一组选一个字符串拼成的字符串在文本串\(s\)出现的个数的所有情况......