题面

给定一张分层有向图,有 \(n\) 层,每层有 \(m\) 个点。只有从第 \(i\) 层的点连向第 \(i + 1\) 层的点的连边。

记 \(A(i,j)\) 表示从第 \(i\) 层的某些点出发到第 \(j\) 层的某些点的点不相交路径集合的最大大小。求

\[\sum_{i=1}^n\sum_{j=i+1}^n A(i,j)
\]

其中,\(n\le 4\times10^4,m\le10\)。


解析

注意到 \(m\) 很小,于是首先有一个暴力状压的思路。\(f(i,S)\) 表示从第 \(i\) 层的 \(S\) 点集出发,要求这 \(|S|\) 条路径都能到达第 \(j\) 层,\(j\) 最大是多少。

可以 DFS 出所有第 \(i\) 层和第 \(i + 1\) 层之间的转移,总复杂度 \(\mathcal{O}(2^{2m}n)\)。统计答案则可以记 \(g(i,k)\):

\[g(i,k)=\max_{|S|=k}f(i,S)
\]

则 \(A(i, j) = k\)(\(g(i, k) \le j \le g(i, k + 1)\)),可以快速统计答案。

考虑这样 DP 的本质是什么。假如把原问题建网络流,只需要把每个点拆点限制流量,就相当于求两层之间的最大流。

而状压 DP 反映的是这样一个结论:从第 \(i\) 层出发,可以在任意位置结束,尽可能流到较大的层;在这样的前提下流最大流,则 \(A(i,j)\) 为流经第 \(j\) 层的流量。

相当于一个费用流,两层之间费用为 \(1\)。(实际上也没有这样去实现代码,只是方便理解)

考虑到这个流网络非常特殊,可以模拟流的过程。

  • 由于可以在任何位置结束,所以一定是满流的,只需要每次找到残留网络的最长路——即能够到达的最深的层
  • 可以直接 BFS,由于费用只和点的层数有关,所以一个点不需要多次更新,过程中维护点的访问标记。
  • 记录增广路的前驱,在找到层数最大的增广路后逆序还原增广路,更新残留网络。
  • 由于流网络分层,不可能从较大的层流向较小的层,于是将源点从第 \(i\) 层换到第 \(i + 1\) 层时,可以直接继承残留网络,删去 \(i\) 和 \(i + 1\) 层之间的连边。

最后就是时间复杂度的问题了。注意到假如在一次增广时,找到的增广路层数为 \(p\),则最多访问 \(pk\) 个点,每个点转移复杂度 \(\mathcal{O}(k)\),则该次增广复杂度为 \(\mathcal{O}(pk^2)\)。

该次增广后,流网络的总流量增加 \(\mathcal{O}(p)\)(手动模拟一下,可以发现即使发生退流,由于我们取层数最大的增广路,退掉的流也会补回来,该次增广仍会使流网络总流量增加 \(p\)),而流网络总流量 \(\mathcal{O(nk)}\),所以 \(\mathcal{O}(\sum pk^2)=\mathcal{nk^3}\)。

模拟细节见代码。


源代码

/* Lucky_Glass */
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm> typedef long long llong;
typedef std::pair<int, int> pii; int rin(int &r) {
int c = getchar(); r = 0;
while (c < '0' || '9' < c) c = getchar();
while ('0' <= c && c <= '9') r = r * 10 + (c ^ '0'), c = getchar();
return r;
} const int N = 4e4 + 10, K = 10; int n, m, nw, tot_flw;
int lnk[N << 1][K], lnk_i[N << 1][K], vis[N << 1];
int elg2[(1 << K) + 10], flw_cnt[N];
pii las_pnt[N << 1][K]; void extend(const int &sx, const int &sy) {
int ed_x = sx, ed_y = sy; int clear_vis_pos = sx;
vis[sx] = 1 << sy;
/* 此次增广经过的第 i 层点(防止环流?) */ std::queue<pii> que;
que.emplace(sx, sy);
while (!que.empty()) {
int ux = que.front().first, uy = que.front().second;
que.pop();
if ((ux & 1) && (ux >> 1) > (ed_x >> 1))
ed_x = ux, ed_y = uy;
/* 向下一层 */
if (ux < nw) {
if (clear_vis_pos == ux) vis[++clear_vis_pos] = 0;
int rem = lnk[ux][uy] ^ (lnk[ux][uy] & vis[ux + 1]);
while (rem) {
int vy = elg2[rem & -rem];
vis[ux + 1] |= 1 << vy;
/* las_pnt 记录转移点,便于还原增广路 */
las_pnt[ux + 1][vy] = std::make_pair(ux, uy);
que.emplace(ux + 1, vy);
rem ^= rem & -rem;
}
}
/* 经过逆向边返回上一层,lnk_i 存储反向边 */
if (ux > sx) {
int rem = lnk_i[ux][uy] ^ (lnk_i[ux][uy] & vis[ux - 1]);
while (rem) {
int vy = elg2[rem & -rem];
vis[ux - 1] |= 1 << vy;
las_pnt[ux - 1][vy] = std::make_pair(ux, uy);
que.emplace(ux - 1, vy);
rem ^= rem & -rem;
}
}
}
/* 更新每一层的流量,更新答案 */
for (int i = sx >> 1, lmt_i = ed_x >> 1; i <= lmt_i; ++i)
++flw_cnt[i], ++tot_flw;
/* 还原增广路,更新残留网络 */
while (ed_x != sx || ed_y != sy) {
// printf("(%d, %d) <- ", ed_x, ed_y);
int lasx = las_pnt[ed_x][ed_y].first,
lasy = las_pnt[ed_x][ed_y].second;
if (ed_x == lasx + 1) {
lnk[lasx][lasy] ^= 1 << ed_y;
lnk_i[ed_x][ed_y] ^= 1 << lasy;
} else {
lnk[ed_x][ed_y] ^= 1 << lasy;
lnk_i[lasx][lasy] ^= 1 << ed_y;
}
ed_x = lasx, ed_y = lasy;
}
// printf("(%d, %d)\n", sx, sy);
}
void init() {
for (int i = 0; i <= K; ++i)
elg2[1 << i] = i;
}
int main() {
freopen("flow.in", "r", stdin);
freopen("flow.out", "w", stdout);
// freopen(".\\input\\input.in", "r", stdin);
init();
rin(n), rin(m);
nw = (n << 1) - 1;
for (int i = 1; i < nw; ++i)
if (i & 1) {
static char inp[K << 1];
for (int j = 0; j < m; ++j) {
scanf("%s", inp);
for (int k = 0; k < m; ++k)
lnk[i][j] |= (inp[k] ^ '0') << k;
}
} else {
for (int j = 0; j < m; ++j)
lnk[i][j] = 1 << j;
} llong ans = 0;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < m; ++j)
extend(std::max(1, (i - 1) << 1), j);
/*
* 注意一定是从 (i - 1) << 1 开始
* 通过 ((i - 1) << 1) -> (((i - 1) << 1) - 1) 是否有边
* 限制每个点只能用一次
* 因为第一层不存在访问多次,可以直接从 1 开始跑
*/
ans += (tot_flw -= flw_cnt[i - 1]);
}
printf("%lld\n", ans);
return 0;
}

THE END

Thanks for reading!

扬起远航的帆
谁在轻声呼唤
萦绕在耳畔
到达心的彼岸
一切宛如梦幻
又归于平淡

——《巫山云》By Snapmod / 诗岸

> Link 巫山云 - 网易云

「SOL」网络流flow (模拟赛)的更多相关文章

  1. 「NOIP2017」「LuoguP3952」 时间复杂度(模拟,栈

    题目描述 小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会来啦!下面请你编写程序 ...

  2. 「CF85E」 Guard Towers

    「CF85E」 Guard Towers 模拟赛考了这题的加强版 然后我因为初值问题直接炸飞 题目大意: 给你二维平面上的 \(n\) 个整点,你需要将它们平均分成两组,使得每组内任意两点间的曼哈顿距 ...

  3. 「CSP-S模拟赛」2019第四场

    「CSP-S模拟赛」2019第四场 T1 「JOI 2014 Final」JOI 徽章 题目 考场思考(正解) T2 「JOI 2015 Final」分蛋糕 2 题目 考场思考(正解) T3 「CQO ...

  4. 「NOWCODER」CSP-S模拟赛第3场

    「NOWCODER」CSP模拟赛第3场 T1 货物收集 题目 考场思路即正解 T2 货物分组 题目 考场思路 题解 60pts 算法:一维 DP 100pts 算法:一维 DP ?线段树 + 单调栈 ...

  5. #10471. 「2020-10-02 提高模拟赛」灌溉 (water)

    题面:#10471. 「2020-10-02 提高模拟赛」灌溉 (water) 假设只有一组询问,我们可以用二分求解:二分最大距离是多少,然后找到深度最大的结点,并且把它的\(k\)倍祖先的一整子树删 ...

  6. #10470. 「2020-10-02 提高模拟赛」流水线 (line)

    题面:#10470. 「2020-10-02 提高模拟赛」流水线 (line) 题目中的那么多区间的条件让人感觉极其难以维护,而且贪心的做法感觉大多都能 hack 掉,因此考虑寻找一些性质,然后再设计 ...

  7. 「NOIP模拟赛」数位和乘积(dp,高精)

    统计方案数,要么组合数,要么递推(dp)了. 这是有模拟赛历史以来爆炸最狠的一次 T1写了正解,也想到开long long,但是开错了地方然后数组开大了结果100->0 T3看错题本来简单模拟又 ...

  8. 「Vijos 1284」「OIBH杯NOIP2006第二次模拟赛」佳佳的魔法阵

    佳佳的魔法阵 背景 也许是为了捕捉猎物(捕捉MM?),也许是因为其它原因,总之,佳佳准备设计一个魔法阵.而设计魔法阵涉及到的最关键问题,似乎就是那些带有魔力的宝石的摆放-- 描述 魔法阵是一个\(n ...

  9. 「CSP-S模拟赛」2019第三场

    目录 T1 「POI2007」山峰和山谷 Ridges and Valleys 题目 考场思路(几近正解) 正解 T2 「JOI 2013 Final」 现代豪宅 题目 考场思路(正解) T3 「SC ...

  10. 「CSP-S模拟赛」2019第一场

    目录 T1 小奇取石子 题目 考场思路 正解 T2 「CCO 2017」专业网络 题目 考场思路 题解 T3 「ZJOI2017」线段树 题目 考场思路 正解 这场考试感觉很奇怪. \(T1.T2\) ...

随机推荐

  1. 【Android 4.4】内存文件系统(tmpfs)的创建与使用

    前言说明 某些情况下,需要缓存一些文件到磁盘中,我们可以借助 tmpfs 文件系统,来提升读写缓存文件的速度,并且也可以避免频繁读写缓存文件所带来的对 flash 的寿命影响. 使用方法 通过 mkd ...

  2. MyBatis-Plus修改数据,会不会把其他字段置为null

    前两天在用MyBatis-Plus写了一张单表的增删改查,在写到修改的时候,就突然蹦出一个奇怪的想法. MyBatis-Plus的BaseMapper中有两个关于修改的方法.如下: int updat ...

  3. BIGO 如何做到夜间同时运行 2.4K 个工作流实例?

    点亮 ️ Star · 照亮开源之路 GitHub:https://github.com/apache/dolphinscheduler   ​ 精彩回顾 近期,BIGO 的大数据研发工程师许名勇在社 ...

  4. 9月23日内容总结——pycharm的安装与使用、python语法规范与注释、变量和常量、索引取值以及部分数据类型简介

    今日内容总结 目录 今日内容总结 一.pycharm的安装 1.软件介绍 2.正版安装 1.下载软件 2.安装软件 3.其他方法安装(需要先下载相关资源) ①无限试用法 ②傻瓜式激活法 ③淘宝购买 二 ...

  5. Python中的函数定义中的斜杠/和星号*

    Python中的函数定义中的斜杠/和星号* 示例 看一段代码  def say_hello(name,age=18):     print(f'你好!我是{name},今年我{age}啦.') say ...

  6. CAS 悲观锁 乐观锁

    前面的偏向锁,轻量级锁,重量级锁都是悲观锁, 都会认为必须要对操作对象进行互斥访问,不然就会产生异常, 所以线程只供一个线程使用,阻塞其他线程,是悲观的 在某些情况下,同步的耗时远大于线程切换的时间, ...

  7. 20个 Git 命令玩转版本控制

    想要在团队中处理代码时有效协作并跟踪更改,版本控制发挥着至关重要的作用.Git 是一个版本控制系统,可以帮助开发人员跟踪修订.识别文件版本,并在必要的时候恢复旧版本.Git 对于有一定编程经验的用户来 ...

  8. JAVA虚拟机17---栈帧(局部变量表-操作数栈-动态连接-返回地址)

    借鉴:转https://blog.csdn.net/u011069294/article/details/107106755,他的虚拟机专栏:https://blog.csdn.net/u011069 ...

  9. 浏览界面servlet实现

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  10. 《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(7)-Charles苹果手机手机抓包知否知否?

    1.简介 Charles和Fiddler一样不但能截获各种浏览器发出的 HTTP 请求,也可以截获各种智能手机发出的HTTP/ HTTPS 请求. Charles也能截获iOS设备发出的请求,比如 i ...