【NOIP2007】矩阵取数问题

题目描述:

帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的(n imes m)的矩阵,矩阵中的每个元素(a(i,j))均为非负整数。
游戏规则如下:
1.每次取数时须从每行各取走一个元素,共(n)个。经过(m)次后取完矩阵内所有元素;

2.每次取走的各个元素只能是该元素所在行的行首或行尾;

3.每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值( imes 2^i) ,其中(i)表示第(i)次取数(从(1)开始编号);

游戏结束总得分为(m)次取数得分之和。

帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。

思路:

看上去是一个矩阵问题,但是实际上,我们可以发现,每一行是相互独立的,也就是说,我们不要从“次”上来研究,而是要根据“行”来研究,最后把每一行的结果相加就可以了。

单独看一行,这个问题就简化成了:有一个序列,只能从两头拿,每次拿的得分为物品值乘上一个权值(和次数有关),要求最终得分最大。

是不是很熟悉?对了,和这道题异曲同工:https://www.luogu.com.cn/problem/P2858 没做过这道题的同学可以先做一下这个简化版。

本题是区间DP老套路了,分从头拿和从尾拿可以分两种情况转移:

(dp[i][j]=max(a[i] * (1<<m-(j-i)) + dp[i+1][j] , a[j] * (1<<m-(j-i)) + dp[i][j-1])), dp[i][j]表示剩下i-j个数时之后能取得的最大值。
然后可以写出这样的程序:

#include<cstdlib>
#include<algorithm>
#define ull unsigned long long
using namespace std;
ull a[81][81],dp[81][81][81];
int main(){
	int i,j,n,m,k;
	ull ans=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			scanf("%lld",&a[i][j]);
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			dp[i][j][j]=a[i][j]*(1ll<<m);
			for(k=j-1;k;k--){
				dp[i][k][j]=max(a[i][k]*(1ll<<m-(j-k))+dp[i][k+1][j],a[i][j]*(1ll<<m-(j-k))+dp[i][k][j-1]);
			}
		}
	}
	for(i=1;i<=n;i++)
		ans+=dp[i][1][m];
	printf("%lld",ans);
	return 0;
}

然后我们就A了这道题。。。
。。。
。。。
吗?
交上去之后发现毒瘤出题人要求高精度,那就重写吧,但是可读性来说绝对是上面的好。

#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
struct BigInt{
	int b[50],rear;
} dp[81][81][81],a[81][81];
void init(BigInt &x){
	for(int i=0;i<50;i++){
		x.b[i]=0;
	}
	x.rear=0;
	return;
}
BigInt operator +(const BigInt x,const BigInt y){
		int flag=0,i;BigInt ans;
		init(ans);
		for(i=0;i<=max(x.rear,y.rear);i++){
			ans.b[i]=x.b[i]+y.b[i]+flag;
			flag=ans.b[i]/10;
			ans.b[i]%=10;
		}
		if(flag){
			ans.b[i]=1;
			ans.rear=1+max(x.rear,y.rear);
		}
		else ans.rear=max(x.rear,y.rear);
		return ans;
	}
BigInt operator *(const BigInt x,const BigInt y){
	int flag=0,i,j;BigInt ans;
	init(ans);
	for(i=0;i<=x.rear;i++){
		for(j=0;j<=y.rear;j++){
			ans.b[i+j]+=x.b[i]*y.b[j];
		}
	}
	ans.rear=x.rear+y.rear;
	for(i=0;i<=x.rear+y.rear;i++){
		ans.b[i]+=flag;
		flag=ans.b[i]/10;
		ans.b[i]%=10;
	}
	ans.rear=i;
	if(flag) ans.b[i]=flag;
	else ans.rear--;
	return ans;
}
BigInt tran(char x[50]){
	BigInt y;
	int l=strlen(x)-1;
	for(int i=0;i<=l;i++)
		y.b[l-i]=x[i]-'0';
	y.rear=l;
	return y;
}
BigInt Max(BigInt x,BigInt y){
	if(x.rear>y.rear) return x;
	else if(y.rear>x.rear) return y;
	else{
		for(int i=x.rear;i>=0;i--){
			if(x.b[i]>y.b[i]) return x;
			else if(y.b[i]>x.b[i]) return y;
		}
	}
	return x;
}
void print(BigInt x){
	for(int i=x.rear;i>=0;i--)
		printf("%d",x.b[i]);
}
BigInt pow[81],two;
int main(){
	int i,j,n,m,k;
	BigInt ans;
	char x[50];
	init(ans);
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++){
			scanf("%s",x);
			a[i][j]=tran(x);
		}
	pow[0].b[0]=1;pow[0].rear=0;
	two.rear=0;two.b[0]=2;
	for(i=1;i<=m;i++){
		pow[i]=two*pow[i-1];
	}
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			dp[i][j][j]=a[i][j]*pow[m];
			for(k=j-1;k;k--){
				dp[i][k][j]=Max(a[i][k]*pow[m-(j-k)]+dp[i][k+1][j],a[i][j]*pow[m-(j-k)]+dp[i][k][j-1]);
			}
		}
	}
	for(i=1;i<=n;i++)
		ans=ans+dp[i][1][m];
	print(ans);
	return 0;
}

高精度题最好用结构体写,函数封装一下,可以使代码更简洁。

原文地址:https://www.cnblogs.com/landmine-sweeper/p/13673110.html