题解:P3974 [TJOI2015] 组合数学

我太菜了,看了好久才会。

题目大意

给出一个 \(n \times m\) 的网格图,网格图上第 \(i\) 行第 \(j\) 列的权值为 \(a_{i,j}\)。每次行动从左上角开始走,只能向右或下走。求最小的行动次数使得点 \((i,j)\) 至少被经过 \(a_{i,j}\) 次。

题目分析

为避免歧义,本文使用 \((x,y)\) 表示网格图中在第 \(x\) 行第 \(y\) 列的点,使用 \(\langle u,v \rangle\) 表示有向图中一条从 \(u\) 连向 \(v\) 的有向边。

这种网格图上的问题我们通常会将其转化为图上问题考虑(虽然有时不会将图建出来)。我们可以把 \((i,j)\) 看作一个图上的点,并从它到它可以到达的点连一条有向边。但这样似乎很难处理每个点至少被经过 \(a_{i,j}\) 次这个条件,于是我们可以考虑建分层图,将 \((i,j)\) 拆为 \(a_{i,j}\) 个点。可是这样拆点,我们会发现权值为 \(0\) 的点就没有了!其实没什么关系,直接将有边连向权值为 \(0\) 的点的点再加上一条连向权值为 \(0\) 的点有边连向的点的边即可。也是就是说,如果 \(\exists\langle u,v\rangle,\langle v,w\rangle \in E\),且 \(v\) 的权值为 \(0\),那么我们就可以看成原图上有一条 \(\langle u,w\rangle\) 的边。然后,对于原图中的边 \(\langle u,v\rangle\),如果 \(u,v\) 在分层图中对应的点的集合分别为 \(U,V\),那么在分层图中我们对于任意的 \(u' \in U,v' \in V\),都连一条 \(\langle u',v'\rangle\) 的边。建好图后,我们可以发现问题的答案正是新图中最小链覆盖(即选出最少的链使图中每个点都在至少一条链里)数。

那么,怎么求这个数呢?我们需要用到 Dilworth 定理。

Dilworth 定理断言:偏序集上的最小链覆盖数正好等于该偏序集上的最大反链大小。与此同时,我们还有一个和它很相似的定理,即偏序集上的最小反链覆盖数正好等于该偏序集上的最大链大小

需要注意的是,这里的链和图论中的链不完全相同。在这里,我们定义偏序集上的链表示偏序集的一个子集,这个子集内的任意两个元素 \(a\)\(b\) 之间都是可以比的,即要么元素 \(a\) 偏序于 \(b\) 要么 \(b\) 偏序于 \(a\)。而反链恰好相反,反链表示偏序集的一个子集,这个子集内任意两个元素 \(a\)\(b\) 之间都是不可比的,即 \(a\) 偏序于 \(b\)\(b\)偏序于 \(a\)

关于 Dilworth 定理的证明,我之前已经很多人证明过了,所以在此不给出。

如果我们把图上 \(u\) 可以直接或间接到达 \(v\) 看作偏序关系(事实上这确实是偏序关系)的话,我们就会发现 Dilworth 定理陈述了这样一个事实:图中最小链覆盖数正是最大的满足集合中任意两个点之间不能直接或间接到达的点集的大小。那么怎么求这个点集的大小呢?我们发现这个分层图中最多可能有 \(10^{12}\) 个点,直接把图建出来显然是行不通的。所以必须考虑其他方法。

看一眼数据范围,\(1 \le n,m \le 10^3\),也就是说矩阵中最大有 \(10^6\) 个元素。时间复杂度大概率是 \(O(n^2)\) 或者 \(O(n^2\log n)\) 的(因为 \(n\)\(m\) 数据范围一样,此处将 \(m\) 认为是与 \(n\) 等价的)。这启发我们直接从矩阵上考虑如何求这个点集的大小。容易发现(真的很容易!),\((i,j)\) 这个点与 \((i-1,j+1)\)\((i+1,j-1)\) 一定互相不可到达,这是因为到达这三个点走的步数必然一样。也就是说,如果点集内选择了 \((i,j)\) 那么 \((i-1,j+1),(i+1,j-1)\) 一定也可以选。而分层图中 \((i,j)\) 会拆成 \(a_{i,j}\) 个点,这些点之间一定也是互相不可到达的(可以由拆点的方式看出)。更进一步的,我们发现 \((1 \sim i-1,j+1 \sim m)\) 中所有点与 \((i,j)\) 都互不可达,\((i+1 \sim n,1 \sim j-1)\) 中所有点与 \((i,j)\) 都互不可达(因为这两种情况想要可达都要么要往上走要么要往左走)。我们还能发现 \((i,j),(i-1,j),(i+1,j),(i,j-1),(i,j+1)\) 这五个点之间最多只能选一个(想想原图中我们是怎么连边的)。考虑进行 DP。我们希望 DP 的转移过程不会有环,并且 \((i,j)\) 处的答案最好不会被转移到它的部分影响。于是我们可以这样设计状态:令 \(f_{i,j}\) 表示只考虑子矩形 \((1 \sim i,j \sim m)\) 时的最大的点集的大小。因为 \((1 \sim i-1,j+1 \sim m)\)\((i,j)\) 都互不可达,所以在 \(f_{i-1,j+1}\) 的基础上再选择 \((i,j)\) 是肯定可行并且比在此基础上不选择 \((i,j)\) 肯定更优(\(0\le a_{i,j}\))。再考虑不选择 \((i,j)\) 时的答案。因为 \(f_{i,j}\) 表示的是只考虑子矩形时的最优解,所以 \(f_{i-1,j}\) 肯定大于等于 \(f_{i-1,j+1}\)。如果要大于 \(f_{i-1,j+1}\),那么肯定要选择 \((1 \sim i-1,j)\) 中的至少一处的点,而这些点都与 \((i,j)\) 都可以间接或直接到达,此时就不能选择 \((i,j)\) 处的点了。而如果相等,那么我们是否考虑从 \(f_{i-1,j}\) 转移也无关紧要了。\(f_{i,j+1}\) 同理。所以我们就可以得出状态转移方程:\(f_{i,j}=\max(f_{i-1,j},f_{i,j+1},f_{i-1,j+1}+a_{i,j})\)。边界条件就是 \(f_{i,0}=f_{0,j}=0\),答案则是 \(f_{n,1}\)(别忘了状态是怎么定义的)。

参考代码

/*********************************************************************
    程序名:P3974
    版权:
    作者: TM_Sharweek
    日期: 2024-11-01 12:38
    说明:I'm the cai_est OIer
*********************************************************************/
#include <bits/stdc++.h>

#define p_b push_back
#define m_p make_pair

using namespace std;
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;

const int N = 1e3 + 50;

ll a[N][N], f[N][N];

int main() {
    freopen(".in","r",stdin);
	freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T;
	cin >> T;
	while (T--) {
		int n, m;
		cin >> n >> m;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				cin >> a[i][j];
			}
		}
		//memset(f, 0, sizeof(f));//不要忘了多测清空哦
		for (int i = 1; i <= n; i++) {
			for (int j = m; j >= 1; j--) {
				f[i][j] = max({f[i - 1][j], f[i][j + 1], f[i - 1][j + 1] + a[i][j]});
			}
		}
		cout << f[n][1];
		//cout << endl;//输出每组数据要换行
	}

	return 1;
}
posted @ 2024-11-26 12:34  御绫军TM_Sharweek  阅读(29)  评论(0)    收藏  举报