最近考试总是DP签到题,本身就是个弱项, 写个小结
蛋糕
大意
Solution
为了简化问题(名称)
先声明“我”是第一个拿蛋糕的那个人
“我”想要得到最大值
定义\(dp[i][j]\)表示取完区间\(i,j\)我能得到的最大值并且最后一次是我取走的
考虑如何转移
对答案有贡献的区间长度为奇数
因为根据定义要保证最后一次是我取的
-
最后一次我取走了\(a[i]\)
那么\(dp[i][j]\)将从状态\(dp[i+1][j]\)转移得到
又因为区间长度为奇数,这一次转移的最后一次是另一个人取的
考虑再往前一步的操作
就是另一个人取走了\(a[i+1]\)或者\(a[j]\)- 假设另一人取走了\(a[i+1]\)
那么上一步的状态变为\(dp[i+2][j]\)
因为在这一步当中状态方程表示的是\(dp[i+2][j]\)证明整段区间已经被选完了
所以另一个人是在\(a[i+1]\)和\(a[j+1]\)中选择了\(a[i+1]\)
当且仅当在\(a[i+1]>a[j+1]\)时成立 - 假设另一人取走了\(a[j]\)
同理推断另一人是在\(a[j]\)和\(a[i]\)当中选择了前者
上一步状态变为\(dp[i+1][j-1]\)
当且仅当在\(a[j] > a[i]\)时成立
- 假设另一人取走了\(a[i+1]\)
-
最后一次我取走了\(a[j]\)
状态将从\(dp[i][j - 1]\)转移得到
另一个人取走了\(a[i]\)或\(a[j-1]\)- 假设另一人取走了\(a[i]\)
区间\(i+1, j\)已经被选完了
状态变为\(dp[i+1][j-1]\)
在\(a[i]>a[j]\)时成立 - 假设另一人取走了\(a[j-1]\)
区间\(i, j-2\)已经被选完了
状态变为\(dp[i][j-2]\)
在\(a[j-1]>a[i-1]\)时成立
- 假设另一人取走了\(a[i]\)
判断边界可以出代码了
Code
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define int long long
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
using namespace std;
inline int read(){
int x = 0, w = 1;
char ch = getchar();
for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
const int ss = 4004;
int dp[ss][ss], a[ss];
signed main(){
register int ans = 0;
register int n = read();
for(register int i = 1; i <= n; i++)
a[n + i] = a[i] = read();
for(register int i = 1; i <= n + n; i++)
dp[i][i] = a[i];
for(register int len = 3; len <= n; len += 2){
for(register int i = 1; i + len - 1 <= n + n; i++){
register int j = i + len - 1;
if(a[i + 1] > a[j + 1] && j + 1 <= n + n && i + 1 <= n + n) dp[i][j] = max(dp[i][j], dp[i + 2][j] + a[i]);
if(a[i] < a[j]) dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + a[i]);
if(a[i] > a[j]) dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + a[j]);
if(a[i - 1] < a[j - 1] && i - 1 >= 1 && j - 1 >= 1) dp[i][j] = max(dp[i][j], dp[i][j - 2] + a[j]);
ans = max(ans, dp[i][j]);
}
}
cout << ans << endl;
return 0;
}
东方记者
大意
在坐标系中给出n个点(信息),以及移动距离的限制
距离表示为曼哈顿距离
问在移动限制内最多能走到多少点(采集多少信息)
Solution
定义\(dp[i][j]\)表示走到第\(i\)个点收集\(j\)个信息的最小的代价
考虑转移
\(dp[j][k]\)到\(dp[i][k+1]\)证明从\(j\)走到了\(i\),收集到了第\(k+1\)个信息
代价为从\(j\)到\(i\)的曼哈顿距离
统计答案
枚举对于每个点能收集\(j\)个信息
如果走到当前点收集\(j\)个信息的代价比限制要少
用\(j\)更新答案即可
Code
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define max(a, b) ({register long long AA = a, BB = b; AA > BB ? AA : BB;})
#define min(a, b) ({register long long AA = a, BB = b; AA < BB ? AA : BB;})
#define getdis(i, j) (abs(x[i] - x[j]) + abs(y[i] - y[j]))
using namespace std;
inline long long read(){
register long long x = 0, w = 1;
char ch = getchar();
for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
const int ss = 105;
long long dp[105][105];
long long x[ss], y[ss];
signed main(){
register int n = read();
memset(dp, 0x7f, sizeof dp);
for(register int i = 1; i <= n; i++){
x[i] = read(), y[i] = read();
dp[i][1] = abs(x[i]) + abs(y[i]);
}
register long long d = read();
for(register int i = 2; i <= n; i++)
for(register int j = 1; j <= i - 1; j++)
for(register int k = 1; k <= j; k++)
dp[i][k + 1] = min(dp[i][k + 1], dp[j][k] + getdis(i, j));
register long long ans = 0;
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= i; j++){
if(abs(x[i]) + abs(y[i]) + dp[i][j] <= d)//在j处收集的代价足够,收集j
ans = max(ans, j);
}
printf("%lld\n", ans);
return 0;
}