【莞工oj原创题目】蓝桥杯国赛特训---奖学金 【题解】

奖学金

抱歉,题目表述有误,导致一些同学读错题目了。但是审核又通过了就改不了题目内容了。
抱歉抱歉!
在这里插入图片描述

输入的内容应该是接下来 1 − n 1 - n 1n行 ,每行有 1 到 m 个 数 1到m个数 1m

常规解法1:动态规划

时间复杂度 O ( N 4 ) O(N^4) O(N4)
一个人时
对于单人,一个经典的题目:走方格
我们知道的DP做法, f ( i , j ) = m a x { f ( i , j − 1 ) , f ( i − 1 , j ) } + a [ i ] [ j ] f(i,j)=max\{f(i,j-1),f(i-1,j)\}+a[i][j] f(i,j)=max{ f(i,j1),f(i1,j)}+a[i][j]
两个人时
那么对于两个人的情况,我们可以加多两个维度,表示另外一个人的走路情况
f ( i , j , k , l ) f(i,j,k,l) f(i,j,k,l) 然后枚举每个人的走路情况,类似第一种做法
f [ i ] [ j ] [ k ] [ l ] = m a x ( f [ i ] [ j − 1 ] [ k ] [ l − 1 ] , f [ i ] [ j − 1 ] [ k − 1 ] [ l ] , f [ i − 1 ] [ j ] [ k − 1 ] [ l ] , f [ i − 1 ] [ j ] [ k ] [ l − 1 ] ) + a [ i ] [ j ] + a [ k ] [ l ] ; f[i][j][k][l] = max(f[i][j-1][k][l-1],f[i][j-1][k-1][l],f[i-1][j][k-1][l],f[i-1][j][k][l-1])+a[i][j]+a[k][l]; f[i][j][k][l]=max(f[i][j1][k][l1],f[i][j1][k1][l],f[i1][j][k1][l],f[i1][j][k][l1])+a[i][j]+a[k][l];

但是空间,时间复杂度可以优化
定义 f ( p , i , j ) f(p,i,j) f(p,i,j)表示当前走了 p p p步,第一个人走到第 i i i行,第二个人走到第j行的最大价值。

显然两个人的坐标都可以计算出来,第一个人是 ( i , p − i + 1 ) (i,p-i+1) (i,pi+1),第二个人是 ( j , p − j + 1 ) (j,p-j+1) (j,pj+1)

转移就考虑两个人的上一步是怎样走的。
f [ p ] [ i ] [ j ] = m a x ( m a x ( f [ p − 1 ] [ i ] [ j ] , f [ p − 1 ] [ i − 1 ] [ j ] ) , m a x ( f [ p − 1 ] [ i ] [ j − 1 ] , f [ p − 1 ] [ i − 1 ] [ j − 1 ] ) ) + a [ i ] [ p − i + 1 ] + a [ j ] [ p − j + 1 ] f[p][i][j] = max(max(f[p - 1][i][j], f[p - 1][i - 1][j]), max(f[p - 1][i][j - 1], f[p - 1][i - 1][j - 1])) + a[i][p - i + 1] + a[j][p - j + 1] f[p][i][j]=max(max(f[p1][i][j],f[p1][i1][j]),max(f[p1][i][j1],f[p1][i1][j1]))+a[i][pi+1]+a[j][pj+1]

三个人时
f ( i , j , k , u ) f(i,j,k,u) f(i,j,k,u)表示第 i i i步时,三个人所在的行数分别为 j , k , u j,k,u jku
三个人的坐标为 ( j , j − i + 1 ) , ( k , k − i + 1 ) , ( u , u − i + 1 ) (j, j - i + 1), (k, k - i + 1),(u, u - i + 1) (j,ji+1),(k,ki+1),(u,ui+1)
从左边或者下边走来, 2 × 2 × 2 2\times 2\times 2 2×2×2,一共8种可能,类似上面的情况,具体见代码

code:

#include<bits/stdc++.h>

using namespace std;
const int N = 125, M = 65;
int f[N][M][M][M], g[M][M], n, m;

int main() {
    
    
    cin >> n >> m;
    int sum = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
    
    
            cin >> g[i][j];
        }
    int i = 1, j = n;
    for (int i = 1; i <= n + m; i++) {
    
    
        for (int j = 1; j <= n && j <= i; j++) {
    
    
            for (int k = 1; k <= n && k <= i; k++) {
    
    
                for (int u = 1; u <= n && u <= i; u++) {
    
    
                    int w = g[j][i - j + 1];
                    if (k != j) w += g[k][i - k + 1];
                    if (u != k && j != u) w += g[u][i - u + 1];
                    int maxv = 0;
                    maxv = max(f[i - 1][j][k - 1][u], maxv);
                    maxv = max(f[i - 1][j][k][u - 1], maxv);
                    maxv = max(f[i - 1][j - 1][k][u], maxv);
                    maxv = max(f[i - 1][j - 1][k - 1][u], maxv);
                    maxv = max(f[i - 1][j - 1][k][u - 1], maxv);
                    maxv = max(f[i - 1][j][k - 1][u - 1], maxv);
                    maxv = max(f[i - 1][j - 1][k - 1][u - 1], maxv);
                    maxv = max(f[i - 1][j][k][u], maxv);
                    f[i][j][k][u] = max(f[i][j][k][u], maxv + w);
                }
            }
        }
    }
    
    cout << f[n + m][n][n][n] << endl;
    return 0;
}

骚操作:图论建模(网络流)

但是我们发现这个题目如果拓展成k个人的情况那该怎么办啊,用动态规划的话,状态又很繁琐和空间不太够,时间直接上升到 O ( n k ) O(n^k) O(nk)
这个时候就需要一点图论的功底了,接下来我们将如何用 M 2 l o g N M^2logN M2logN解决此类问题的。

前置知识

如何将这个题目转化为一个网络流题目呢
对于一个格子我们只能取一个数对吧,也就是说取完了就不能再取了,但是队友还得路过那里啊。怎么办呢?
我们将格子拆分成两个,具体来说就是将改点分裂成两个,一个是开始取得点(入点),一个是去后的点(出点),将该点的权值变为连边费用,因为只能一个人取啊,所以我们将该入点出点之间的之间额的流量改为 1 1 1
那该格子走到另外一个格子呢,我们可以将该点的出点与另外一个入点连边,费用为 0 0 0,流量为无穷(因为可以重复走,代价为0),源点与第一个点连代价为 0 0 0的,流量为 k k k的边,汇点与第一个点连代价为 0 0 0的,流量为 k k k的边.
跑一下最大费用最大流即可啦。
对于网络流模板不难,建模难!

建模后类似此图(图画得很丑,不完整,见谅,主要表达思想)
跑一下网络最大费用最大流即可
在这里插入图片描述

code:

#include<bits/stdc++.h>

using namespace std;

const int INF=0x3f3f3f3f;
const int N=1e5+100;
int head[N],ver[N],nex[N],edge[N],cost[N];
int tot=1;
void addedge(int x,int y,int z,int c){
    
    
	ver[++tot]=y,nex[tot]=head[x],cost[tot]=c;
	edge[tot]=z,head[x]=tot;
}

void add(int x,int y,int z,int c){
    
    
	addedge(x,y,z,c);
	addedge(y,x,0,-c);
}

//以下费用流基操,不介绍
int maxflow=0,maxcost=0;
int n,m,s,t;
int d[N];
int in[N];
int incf[N];
int pre[N];
bool spfa(){
    
    
	queue<int> q;
	memset(d,0xef,sizeof(d));
	memset(in,0,sizeof(0));
	q.push(s);
	d[s]=0,in[s]=1;
	incf[s]=INF;
	while(!q.empty()){
    
    
		int x=q.front();q.pop();
		in[x]=0;
		for(int i=head[x];i;i=nex[i]){
    
    
			int y=ver[i];
			if(!edge[i]) continue;
			if(d[y]<d[x]+cost[i]){
    
    
				d[y]=d[x]+cost[i];
				incf[y]=min(incf[x],edge[i]);
				pre[y]=i;
				if(!in[y])  q.push(y),in[y]=1;
			}
		}
	}
	if(d[t]==0xefefefef) return 0;
	return 1;
}

void update(){
    
    
	int x=t;
	while(x!=s){
    
    
		int i=pre[x];
		edge[i]-=incf[t];
		edge[i^1]+=incf[t];
		x=ver[i^1];
	}
	maxflow+=incf[t];
	maxcost+=incf[t]*d[t];
}

int num(int i,int j){
    
    
	return (i-1)*m+j;
}

int main(){
    
    
	int k=3; //三个人的情况
	cin>>n>>m;
	s=3*n*m;t=s+1; //源点,汇点
	int p=n*m;
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			int x;
			cin>>x;
			add(num(i,j),num(i,j)+p,1,x);
			add(num(i,j),num(i,j)+p,INF,0);
			if(i+1<=n) add(num(i,j)+p,num(i+1,j),INF,0);
			if(j+1<=m) add(num(i,j)+p,num(i,j+1),INF,0); 
		}
	}
	add(s,num(1,1),k,0);
	add(num(n,m)+p,t,k,0);



	while(spfa()) update();
	cout<<maxcost<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43601905/article/details/109394488
今日推荐