关于取数的题目(区间dp)
关于取数的题目(区间dp)
首先我们来看一道简单的取数问题:
取数游戏:有三个数字,把他们组成一个最大的三位数,暴力就行了
代码如下:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll INF = -1e9; const ll N =80+7; const ll mod = 9999999967; inline ll read() { ll s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } inline void write(ll x) { if (!x) { putchar('0'); return; } char F[200]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-'); int cnt = 0; while (tmp > 0) { F[cnt++] = tmp % 10 + '0'; tmp /= 10; } while (cnt > 0)putchar(F[--cnt]); } inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; } ll qpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans *= a;b >>= 1;a *= a; } return ans; } ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; } inline int lowbit(int x) { return x & (-x); } int main(){ int a,b,c,t; cin>>t; while(t--){ cin>>a>>b>>c; if(a<b) swap(a,b); if(a<c) swap(a,c); if(b<c) swap(b,c); printf("%d%d%d\n",a,b,c); } }
接下来的就是dp的题目了:
取数游戏2
题目描述
给定两个长度为n的整数列A和B,每次你可以从A数列的左端或右端取走一个数。假设第i次取走的数为ax,则第i次取走的数的价值vi=bi⋅ax,现在希望你求出∑vi的最大值。
题解:
这是一道区间dp问题,dp[i][j]代表左边取i个,右边取j个能获得的最大价值
显然有递推式:dp[i][j]=max(dp[i-1][j]+a[i]b[i+j],dp[i][j-1]+a[n-j+1]b[i+j])
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 1e3+7; const ll MOD = 1e9 + 7; int a[MAXN],b[MAXN]; int dp[MAXN][MAXN]; int main(){ int t; cin>>t; while(t--){ memset(dp,0,sizeof(dp)); int n; cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) cin>>b[i]; for(int i=1;i<=n;i++){ dp[i][0]=dp[i-1][0]+a[i]*b[i];//左边取i-1个,右边取0个 dp[0][i]=dp[0][i-1]+a[n-i+1]*b[i]; }//因为下面是从左右都取一个开始递推 int ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ dp[i][j]=max(dp[i-1][j]+a[i]*b[i+j],dp[i][j-1]+a[n-j+1]*b[i+j]); if(i+j==n) ans=max(ans,dp[i][j]); } } cout<<ans<<endl; } return 0; }
题目描述:
对于一个给定的n*m的矩阵,矩阵中的每个元素aij均为非负整数。游戏规则如下:
1.每次取数时须从每行各取走一个元素,共n个。m次后取完矩阵所有元素;
2.每次取走的各个元素只能是该元素所在行的行首或行尾;
3.每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值 * 2i,其中i表示第i次取数(从1开始编号);
4.游戏结束总得分为m次取数得分之和。
题解:
其实这道题目就相当于取数游戏2的加强版,因为每行取数的结果互不影响。需要注意的是会爆long long ,需要开_int128_t,
递推式:
dp[k][i][j]=max(dp[k][i-1][j]+a[k][i]qpow(2,i+j),dp[k][i][j-1]+a[k][m-j+1]qpow(2,i+j))
代码如下:
#include<bits/stdc++.h> using namespace std; typedef __int128_t ll; const ll INF = -1e9; const ll N =80+7; const ll mod = 9999999967; inline ll read() { ll s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } inline void write(ll x) { if (!x) { putchar('0'); return; } char F[200]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-'); int cnt = 0; while (tmp > 0) { F[cnt++] = tmp % 10 + '0'; tmp /= 10; } while (cnt > 0)putchar(F[--cnt]); } inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; } ll qpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans *= a;b >>= 1;a *= a; } return ans; } ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; } inline int lowbit(int x) { return x & (-x); } inline void print(ll x){ if(x>9)print(x/10); putchar('0'+x%10); } ll dp[N][N][N], a[N][N]; ll ans[N]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read(); for(int k=1;k<=n;k++) for(int i=1;i<=m;i++){ dp[k][i][0]=dp[k][i-1][0]+a[k][i]*qpow(2,i); dp[k][0][i]=dp[k][0][i-1]+a[k][m-i+1]*qpow(2,i); } for(int k=1;k<=n;k++){ for(int i=1;i<=m;i++){ for(int j=1;j<=m;j++){ dp[k][i][j]=max(dp[k][i-1][j]+a[k][i]*qpow(2,i+j),dp[k][i][j-1]+a[k][m-j+1]*qpow(2,i+j)); if(i+j==m) ans[k]=max(ans[k],dp[k][i][j]); } } } ll sum=0; for(int i=1;i<=n;i++) sum+=ans[i]; print(sum); }
题目描述:
给你一个n*n的方格,每个位置都有一个数字(正整数或者0),有个人从起点(1,1)开始走到终点(n,n),他只能向下或者向右走,走过每个有正整数方格都可以选择是否拿走那个正整数(注意,拿走正整数后那个方格的数字变成0),题目要你求这个人走两遍方格能拿走的最大数字和。
题解:
可以看成是两个人同时从起点往终点走
两个人在同一个位置的时候:dp[i][j][k][p]=max(max(dp[i-1][j][k-1][p],dp[i-1][j][k][p-1]),max(dp[i][j-1][k-1][p],dp[i][j-1][k][p-1]))+a[i][j]
两个人不在同一个位置的时候:dp[i][j][k][p]=max(max(dp[i-1][j][k-1][p],dp[i-1][j][k][p-1]),max(dp[i][j-1][k-1][p],dp[i][j-1][k][p-1]))+a[i][j]+a[k][p]
代码如下:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll N = 15; inline ll read() { ll s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } inline void write(ll x) { if (!x) { putchar('0'); return; } char F[200]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-'); int cnt = 0; while (tmp > 0) { F[cnt++] = tmp % 10 + '0'; tmp /= 10; } while (cnt > 0)putchar(F[--cnt]); } inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; } ll qpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans *= a; b >>= 1; a *= a; } return ans; } ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; } inline int lowbit(int x) { return x & (-x); } int a[N][N],dp[N][N][N][N]; int main() { int n; cin>>n; int x,y,z; while(cin>>x>>y>>z){ if(x==0&&y==0&&z==0) break; a[x][y]=z; } dp[1][1][1][1]=a[1][1]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) for(int p=1;p<=n;p++) if(i==k&&j==p){ dp[i][j][k][p]=max(max(dp[i-1][j][k-1][p],dp[i-1][j][k][p-1]),max(dp[i][j-1][k-1][p],dp[i][j-1][k][p-1]))+a[i][j]; } else dp[i][j][k][p]=max(max(dp[i-1][j][k-1][p],dp[i-1][j][k][p-1]),max(dp[i][j-1][k-1][p],dp[i][j-1][k][p-1]))+a[i][j]+a[k][p]; cout<<dp[n][n][n][n]<<endl; }
题目描述:
设有 𝑛 × 𝑚 的方格图,每个方格中都有一个整数。现有一只小熊,想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,并且不能重复经过已经走过的方格,也不能走出边界。小熊会取走所有经过的方格中的整数,
求它能取到的整数之和的最大值。
题解:
这题和上一道题目类似,只是多了一种情况,可以向上走,同时也只走一遍。
开始的时候我的思路是从上往下,从左往右dp遍历,但是发现了只能考虑到向下,向右的情况,然后错了,之后看了别人的代码,用了两个数组,一个存向下,向右的方案,一个存向上向右的方案,比较更优的解即可。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll INF = -1e9; const ll NUM =1e4+7; const ll mod = 9999999967; inline ll read() { ll s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } inline void write(ll x) { if (!x) { putchar('0'); return; } char F[200]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-'); int cnt = 0; while (tmp > 0) { F[cnt++] = tmp % 10 + '0'; tmp /= 10; } while (cnt > 0)putchar(F[--cnt]); } inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; } ll qpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans *= a;b >>= 1;a *= a; } return ans; } ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; } inline int lowbit(int x) { return x & (-x); } int g[NUM][NUM],f[NUM][NUM],a[NUM][NUM]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j]; for(int i=0;i<=n+1;i++) for(int j=0;j<=m+1;j++) g[i][j]=f[i][j]=INF; f[1][1]=a[1][1]; for(int j=1;j<=m;j++){ for(int i=1;i<=n;i++) g[i][j]=f[i][j]=max(f[i][j],f[i][j-1]+a[i][j]);//往右走 for(int i=1;i<=n;i++) f[i][j]=max(f[i][j],f[i-1][j]+a[i][j]);//往下走 for(int i=n;i>=1;i--) g[i][j]=max(g[i][j],g[i+1][j]+a[i][j]);//往上走 for(int i=1;i<=n;i++) f[i][j]=max(g[i][j],f[i][j]); } cout<<f[n][m]<<endl; }