题解 P3974【[TJOI2015]组合数学】

posted on 2022-10-28 14:11:41 | under 题解 | source

problem

给出一个网格图,其中某些格子有财宝,每次从左上角出发,只能向下或右走。问至少走多少次才能将财宝捡完。此对此问题变形,假设每个格子中有好多财宝,而每一次经过一个格子至多只能捡走一块财宝,至少走多少次才能把财宝全部捡完。

\(n,m\leq 10^3,V\leq 10^6\)

solution

建模:以右下角 \((n,1)\) 为原点,建立平面直角坐标系。将每一财宝拍成一个点,放在平面上,如图:

3 1 5 2
2 2 1 2
4 0 3 3

我们希望将“走一次”变成这样的一个问题:选出一些点 \(\{(x_i,y_i)\}\),使得对于任意 \(x_i<x_j\) 都有 \(y_i\geq y_j\)。我们把一个点的宝藏斜放在一个格子里保证了“走过”这个格子只会取走一个。

把它拍到序列上,相当于需要多少个单调不增的子序列覆盖这些点。

Dilworth 定理

  • 对于一个偏序集,其最少反链划分数等于其最大链的大小。
  • 对于一个偏序集,其最少链划分数等于其最大反链的大小。

应用到这道题,就是 单调不增的子序列的数量 = 最长单调递增的子序列的长度。

于是直接 dp 就可以了。

code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int n,m,a[1010][1010];
LL f[1010][1010];
int mian(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
	}
	for(int j=1;j<=m;j++){
		for(int i=n;i>=1;i--){
			f[i][j]=max({f[i+1][j-1]+a[i][j],f[i][j-1],f[i+1][j]});
		}
	}
	printf("%lld\n",*max_element(&f[1][1],&f[n][m]));
	return 0;	
}
int main(){
	for(scanf("%*d");~scanf("%d%d",&n,&m);mian());
	return 0;
}

证明

考虑导弹拦截,现在有很多个系统拦截了 \([1,i)\) 的导弹。欲将拦截 \(i\) 号导弹,我们一定是选当前最低的一个系统去拦截。如果导弹飞的比系统还高,那就给它开个系统。这样就感性地证明了 Dilworth 定理。

posted @ 2022-11-10 16:18  caijianhong  阅读(17)  评论(0编辑  收藏  举报