dp(7/100)
裸dp题,还记得去年我准备转专业的时候,那时候语法都不怎么会,随便听到个动态规划的词上网上搜着学,愚笨的我怎么啃都不明白。稀里糊涂跌跌撞撞混过一年,或许还是什么都没有学会。
对于走格子这种问题,知乎上有一篇写的很好。我去年看这篇文章,才大概的懂了一些问题
#include
using namespace std;
const int N = 1010;
int f[N][N];
int n, m;
void solve()
{
memset(f, 0, sizeof f);
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ ) cin >> f[i][j];
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
f[i][j] += max(f[i - 1][j], f[i][j - 1]);
cout << f[n][m] << endl;
}
int main()
{
int t;
cin >> t;
while(t -- ) solve();
return 0;
}
dp(8/100)
#include
using namespace std;
const int N = 110;
int f[N][N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ ) cin >> f[i][j];
for (int i = 1; i <= n; i ++ ){
f[i][1] += f[i - 1][1];
f[1][i] += f[1][i - 1];
}
for (int i = 2; i <= n; i ++ )
for (int j = 2; j <= n; j ++ )
f[i][j] += min(f[i - 1][j], f[i][j - 1]);
cout << f[n][n] << endl;
return 0;
}
(下面是两道不那么裸的)
dp(10/100)
i1 + j1 == i2 + j2 的时候可以两人走的步数是一样的,所以记 k = i1 + j1 = i2 + j2,这样就可以把四维的f[i1][j1][i2][j2]优化成三维的f[k][i1][i2]。
#include
using namespace std;
const int N = 11;
int f[N + N][N][N], w[N][N];
int main()
{
int n;
cin >> n;
int a, b, c;
while (cin >> a >> b >> c, a | b | c) w[a][b] = c;
for (int k = 2; k <= n + n; 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 > n || j2 < 1 || j2 > n) continue;
int &x = f[k][i1][i2];
int t = w[i1][j1];
if (i1 != i2) t += w[i2][j2]; //这两个点不重合的话就能获得两个格子里的数
x = max(x, f[k - 1][i1 - 1][i2 - 1] + t); //两个人都向下走
x = max(x, f[k - 1][i1 - 1][i2] + t); //第一个人向下走,第二个人向右走
x = max(x, f[k - 1][i1][i2 - 1] + t); //第一个人向右走,第二个人向下走
x = max(x, f[k - 1][i1][i2] + t); //两个人都向右走
}
cout << f[n + n][n][n] << endl;
return 0;
}
dp(11/100)
题目说一个人从左上往右下传 一个人从右下往左上传,其实跟两个人一起从左上往右下传是一样的。与上一题相比有个这道题多的条件是,两人所走路线不能重合。
#include
// #pragma GCC optimize(3,"Ofast","inline")
// #pragma GCC optimize(2)
using namespace std;
#define int long long
const int N = 55;
int f[N + N][N][N];
int w[N][N];
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ ) cin >> w[i][j];
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 (i1 == i2)
if (i1 == 1 && j1 == 1 || i1 == n && j1 == m);
else continue;
if (j1 < 1 || j1 > m || j2 < 1 || j2 > m) continue;
int &x = f[k][i1][i2];
int t = w[i1][j1] + w[i2][j2];
x = max(x, f[k - 1][i1 - 1][i2 - 1] + t);
x = max(x, f[k - 1][i1 - 1][i2] + t);
x = max(x, f[k - 1][i1][i2 - 1] + t);
x = max(x, f[k - 1][i1][i2] + t);
}
cout << f[n + m][n][n] << endl;
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(nullptr);cout.tie(nullptr);
int t = 1;
// cin >> t;
while(t -- ) solve();
system("pause");
return 0;
}
dp(12/100)
题意为在一个数组中选一个点,选择向左或者向右的最长下降子序列。
正反求一遍最长上升子序列取max就能解决
#include
using namespace std;
const int N = 110;
int f[N];
int a[N];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
int res = 0;
for (int i = 1; i <= n; i ++ ){
f[i] = 1;
for (int j = 1; j < i; j ++ )
if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
for (int i = n; i; i -- ){
f[i] = 1;
for (int j = n; j > i; j -- )
if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res << endl;
}
int main()
{
int t;
cin >> t;
while (t -- ) solve();
return 0;
}
dp(13/100)
#include
using namespace std;
const int N = 1010;
int a[N], up[N], down[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ){
up[i] = 1;
for (int j = 1; j < i; j ++ )
if (a[j] < a[i]) up[i] = max(up[i], up[j] + 1);
}
for (int i = n; i; i -- ){
down[i] = 1;
for (int j = n; j > i; j -- )
if (a[i] > a[j]) down[i] = max(down[i], down[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i ++ ){
res = max(res, down[i] + up[i] - 1);
}
cout << res << endl;
return 0;
}
dp(14/100)
#include
using namespace std;
const int N = 1010;
int a[N], up[N], down[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ){
up[i] = 1;
for (int j = 1; j < i; j ++ )
if (a[j] < a[i]) up[i] = max(up[i], up[j] + 1);
}
for (int i = n; i; i -- ){
down[i] = 1;
for (int j = n; j > i; j -- )
if (a[i] > a[j]) down[i] = max(down[i], down[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i ++ ){
res = max(res, down[i] + up[i] - 1);
}
cout << n - res << endl;
return 0;
}
dp(15/100)
这道题关键看出选择一边排序,他们的友好城市一定也是非递减关系的,否则就会有两桥交叉。
#include
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 5010;
PII p[N];
int f[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> p[i].x >> p[i].y;
sort(p + 1, p + 1 + n);
int res = 0;
for (int i = 1; i <= n; i ++ ){
f[i] = 1;
for (int j = 1; j < i; j ++ )
if (p[i].y > p[j].y) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
dp(16/100)
#include
using namespace std;
const int N = 1010;
int f[N], a[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
int res = 0;
for (int i = 1; i <= n; i ++ ){
f[i] = a[i];
for (int j = 1; j < i; j ++ )
if (a[j] < a[i]) f[i] = max(f[i], f[j] + a[i]);
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
dp(17/100)
N = 1000
#include
using namespace std;
const int N = 1010;
int f[N], a[N];
int n;
int main()
{
while(cin >> a[n]) n ++;
int res = 0;
for (int i = n - 1; i >= 0; i -- ){
f[i] = 1;
for (int j = n - 1; j > i; j --)
if (a[j] <= a[i]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res << endl;
res = 0;
for (int i = 0; i < n; i ++ ){
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
N = 100000
其实这就变成贪心问题了,跟动态规划关系不大
#include
using namespace std;
const int N = 100010;
int f[N], a[N], q[N];
int n;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
while(cin >> a[n]) n ++;
int res = 0;
for (int i = 0; i < n; i ++ ){
int l = 0, r = res;
while(l < r){
int mid = l + r + 1 >> 1;
if(f[mid] >= a[i]) l = mid;
else r = mid - 1;
}
res = max(res, l + 1);
f[r + 1] = a[i];
}
cout << res << endl;
res = 0;
for (int i = 0; i < n; i ++ ){
int l = 0, r = res;
while(l < r){
int mid = l + r + 1 >> 1;
if(q[mid] < a[i]) l = mid;
else r = mid - 1;
}
res = max(res, l + 1);
q[r + 1] = a[i];
}
cout << res << endl;
return 0;
}
dp(20/100)
f[i][j]表示字符串a的前i个字母和b的前j个字母的最大公共子序列的长度。状态转化对于包不包含a[i] , b[j]分为4种情况。
#include
using namespace std;
const int N = 1010;
char a[N], b[N];
int f[N][N], n, m;
int main()
{
cin >> n >> m >> a + 1 >> b + 1;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j<= m; j ++ ){
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
}
cout << f[n][m] << endl;
return 0;
}
dp(21/100)
f[i][j]表示a的前i个字母,b的前j个字母,并且以b[j]结尾的最长公共上升子序列长度
#include
using namespace std;
const int N = 3010;
int a[N], b[N], f[N][N];
int main()
{
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 ++ ){
int ma = 0; //a[i - 1]大于b[j - 1](也就是说如果a[i]=b[j]可以直接在此基础上加1)的最长上升子序列值
for (int j = 1; j <= n; j ++ ){
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = max(f[i][j], ma + 1);
else if (b[j] < a[i]) ma = max(ma, f[i - 1][j]);
}
}
int res = 0;
//遍历b以任意处结尾的最大值
for (int i = 1; i <= n; i ++ ){
res = max(res, f[n][i]);
}
cout << res << endl;
return 0;
}