数字三角形模型

Demo大约 6 分钟

求从左上角到右下角走一次的最大或最小值

有一个n*m的网格,每个网格都有一个数,给你规定可以走的方向和走的次数,求从左上角到右下角的最大或者最小值。

常规的一般是可以向下或者向右走,走一次之后求从左上角到右下角的最大值或者最小值,那么状态转移的方程就是:
f[i][j]=max(f[i][j],f[i-1][j]);
f[i][j]=max(f[i][j],f[i][j-1]);

从左上角到右下角走两次的最大值

例题:https://www.acwing.com/problem/content/1029/open in new window
题意:一个n*n的格子每个格子里都有一个数,从(1,1)到(n,n)走两条路径,走过的路径的格子里的数被拿走,每个格子里的数只能被拿一次,每次只能向下或者向右走,求走两条路径拿的数的和的最大值。
思路:
不能按第一条路最优第二条路最优的方式分开求,因为第一条路最优的时候第二条路不一定最优。那么就考虑两条路同时走。
f[i1][j1][i2][j2]:表示第一条路和第二条路同时走,第一条路从(1,1)走到(i1,j1),第二条路从(1,1)走到(i2,j2)时,所有路径的最大值。

四重循环肯定会超时或者超内存,所以我们需要优化一维。
从(1,1)到(n,n)同时走,要么向下要么向右,实际上都是横坐标或者是纵坐标+1,那么两条路同时走到的格子,横纵坐标相加就是一个固定值。那么我们就用一维k来表示横纵坐标相加的值。

如何去处理同一个格子不能被重复选择的情况:
当i1+j1==i2+j2时,两条路的格子可能重合。
然而其实两个格子同时走的话,i1+j1恒等于i2+j2,因为要么两条路横坐标+1或者纵坐标+1,那么相加的和都是一样的。
那么我们就可以优化到三维:
f[k][i1][i2]:k表示i1+j1的和,也等于i2+j2的和,i1是从第一条路走到的点的横坐标,i2是从第二条路走到的点的纵坐标。
那么状态表示就是:两条路同时走,第一条路从(1,1)开始走到(i1,k-i1),第二条路从(1,1)开始走到(i2,k-i2)的最大值。

每个路线都可以向下走或者向右走,那么两条路线就有四种情况:
1.第一条向下,第二条向下:f[k-1][i1-1][i2-1]
2.第一条向下,第二条向右:f[k-1][i1-1][i2]
3.第一条向右,第二条向下:f[k-1][i1][i2-1]
4.第一条向右,第二条向右:f[k-1][i1][i2]

那么当走到(i1,j1)和(i2,j2)的时候,当两个格子在同一个地方那么就加一个格子,在不同地方就都加上。

#include<bits/stdc++.h>
using namespace std;
const int N=20;
int f[N][N][N];
int a[N][N];
int n;
int main(){
	cin>>n;
	int i,j,k;
	while(cin>>i>>j>>k,i||j||k){
		a[i][j]=k;
	}
	memset(f,-0x3f,sizeof f);
	f[2][1][1]=a[1][1];
	for(int k=2;k<=n*2;k++){
		for(int i1=1;i1<=n;i1++){
			for(int i2=1;i2<=n;i2++){
				int &x=f[k][i1][i2];
				int j1=k-i1,j2=k-i2;
				int t=a[i1][j1];
				if(i1!=i2) t+=a[i2][j2];
				x=max(x,f[k-1][i1][i2]+t);
				x=max(x,f[k-1][i1-1][i2-1]+t);
				x=max(x,f[k-1][i1][i2-1]+t);
				x=max(x,f[k-1][i1-1][i2]+t);
				
			}
		}
	}
	cout<<f[n*2][n][n]<<endl;
	return 0;
}

变形

例题:https://www.acwing.com/problem/content/277/open in new window
题意:一个人从左上角到右下角,另一个人从右下角到左上角,每个格子都有一个数值,但是每个格子只能被走一次,不能被两个人走。第一个人只能往下或往右,第二个人只能向上或向左,求他们到达目的地的最大值。

思路:
因为每个格子只能走一次,那么这两条路不互相影响,那么第二个人从右下角到左上角其实也可以看成从左上角到右下角。
那么可以设状态为f[k][i1][i2]来表示同时从(1,1)走到(i1,j1)和(i2,j2)的时候取到的最大值。
那么有个问题就是不能走到同一个格子,那么我们当枚举到f[k][i1][i2]时,如果(i1,j1)和(i2,j2)是同一个格子就将这个状态设为-INF,这样后续的状态转移就不会用这个状态来转移,但是注意(1,1)和(n,m)这两个格子是都需要走的,那么就特判一下当两个格子相同且不是(1,1)和(n,m)的情况下状态设为-INF,其他的和上个题的思路一样。

#include<bits/stdc++.h>
using namespace std;
const int N=100;
int f[N][N][N];
int a[N][N];
int n,m;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	memset(f,-0x3f,sizeof f);
	f[2][1][1]=a[1][1];
	for(int k=2;k<=n+m;k++){
		for(int i1=1;i1<=n;i1++){
			for(int i2=1;i2<=n;i2++){
				int j1=k-i1,j2=k-i2;
				if(j1>=1&&j1<=m&&j2>=1&&j2<=m){
					int &x=f[k][i1][i2];
					if(i1==i2&&k!=2&&k!=n+m){
						x=-0x3f3f3f3f;
						continue;
					}
					int t=a[i1][j1]+a[i2][j2];
					x=max(x,f[k-1][i1-1][i2-1]+t);
					x=max(x,f[k-1][i1][i2]+t);
					x=max(x,f[k-1][i1-1][i2]+t);
					x=max(x,f[k-1][i1][i2-1]+t);
				}
				
			}
		}
	}
	cout<<f[n+m][n][n]<<endl;
	return 0;
}

免费馅饼

原题链接:https://acm.hdu.edu.cn/showproblem.php?pid=1176open in new window
题意:
有一个0~10的坐标轴,一个人刚开始在下标为5的位置上,天上要掉n个馅饼,在t秒的时候在位置为x的地方掉一个馅饼,但是这个人每次只能向左或向右走一步,亦或者是待在原地,求他最多能接到多少个馅饼。

思路:
f[i][j]表示在第i秒的时候在位置j的地方接到的馅饼的最大值,那么状态上一秒在j,或者j-1,或者j+1,那么状态转移方程就是:
f[i][j]=max(f[i][j],f[i-1][j]+a[i][j]);
f[i][j]=max(f[i][j],f[i-1][j-1]+a[i][j]);
f[i][j]=max(f[i][j],f[i-1][j+1]+a[i][j]);

判断一下边界情况即可。

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e5+10;
int f[N][20];
int a[N][20];
int main(){
	while(cin>>n,n){
		memset(f,-0x3f,sizeof f);
		memset(a,0,sizeof a);
		f[0][5]=0;
		int mx=0;
		for(int i=1;i<=n;i++){
			int t,x;
			cin>>x>>t;
			mx=max(t,mx);
			a[t][x]++;
		}
		for(int i=1;i<=mx;i++){
			for(int j=0;j<=10;j++){
				f[i][j]=max(f[i][j],f[i-1][j]+a[i][j]);
				if(j-1>=0&&j-1<=10)f[i][j]=max(f[i][j],f[i-1][j-1]+a[i][j]);
				if(j+1>=0&&j+1<=10)f[i][j]=max(f[i][j],f[i-1][j+1]+a[i][j]);
			}
		}
		int ans=0;
		for(int i=0;i<=10;i++) ans=max(ans,f[mx][i]);
		cout<<ans<<endl;
	}
	return 0;
}

Loading...