复习 [kuangbin带你飞]专题12 基础DP1

目录

  • 1. hdu 1024 Max Sum Plus Plus
  • 2. hdu 1029 Ignatius and the Princess IV
  • 3. hdu 1069 Monkey and Banana
  • 4. hdu 1074 Doing Homework
  • 5. hdu 1087 Super Jumping! Jumping! Jumping!
  • 6. hdu 1114 Piggy-Bank
  • 7. hdu 1176 免费馅饼
  • 8. hdu 1260 Tickets
  • 9. hdu 1257 最少拦截系统
  • 10. hdu 1160 FatMouse's Speed
  • 12. poj 1458 Common Subsequence
  • 13. poj 1661 Help Jimmy
  • 14. poj 2533 Longest Ordered Subsequence
  • 15. poj 3186 Treats for the Cows
  • 16. hdu 1078 FatMouse and Cheese
  • 17. hdu 2859 Phalanx
  • 18. poj 3616 Treats for the Cows
  • 19. poj 3666 Making the Grade

1. hdu 1024 Max Sum Plus Plus

求把数组分成m段的最大和,也可以相邻

  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示把前 i i i个数分成 j j j段,且以第 i i i个数结尾的最大和, d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] + a [ i ] , d p [ k ] [ j − 1 ] + a [ i ] ) , j − 1 ≤ k ≤ i − 1 dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i],dp[k][j-1]+a[i]),j-1\leq k\leq i-1 dp[i][j]=max(dp[i][j],dp[i1][j]+a[i],dp[k][j1]+a[i]),j1ki1,就是它和前一个数相邻或者不相邻这两种情况
  • 但是这样的话时间和空间都会超,考虑优化
  • k k k的作用是找 i i i的上一层 i − 1 i-1 i1中最大的情况,我们可以通过记录一个数组的形式压掉这一层循环
  • 可以写出代码如下
#include 

using namespace std;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int m, n;
  while(cin >> m >> n) {
    vector<int> a(n + 1);
    for(int i=1;i<=n;i++) {
      cin >> a[i];
    }
    vector<vector<int> > dp(n + 1, vector<int>(m + 1));
    vector<int> dp0(n + 1);
    for(int j=1;j<=m;j++) {
      int mx = INT_MIN;
      for(int i=j;i<=n;i++) {
        dp[i][j] = max(dp[i - 1][j] + a[i], dp0[i - 1] + a[i]);
        dp0[i - 1] = mx;
        mx = max(mx, dp[i][j]);
      }
    }
    cout << dp[n][m] << '\n';
  }
  return 0;
}
  • 但还是无法通过本题,继续优化,发现 d p dp dp数组中的第二维始终是 j j j,所以直接去掉
#include 

using namespace std;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int m, n;
  while(cin >> m >> n) {
    vector<int> a(n + 1);
    for(int i=1;i<=n;i++) {
      cin >> a[i];
    }
    vector<int> dp(n + 1);
    vector<int> dp0(n + 1);
    int mx;
    for(int j=1;j<=m;j++) {
      mx = INT_MIN;
      for(int i=j;i<=n;i++) {
        dp[i] = max(dp[i - 1] + a[i], dp0[i - 1] + a[i]);
        dp0[i - 1] = mx;
        mx = max(mx, dp[i]);
      }
    }
    cout << mx << '\n';
  }
  return 0;
}

2. hdu 1029 Ignatius and the Princess IV

  • 此题可使用摩尔投票法
#include 

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    while(cin >> n){
        int ans = -1;
        int cnt = 0;
        for(int i=0;i<n;i++){
            int x;
            cin >> x;
            if(cnt == 0) ans = x;
            if(ans == x) cnt += 1;
            else cnt -= 1;
        }
        cout << ans << '\n';
    }
    return 0;
}

大致原理就是如果一个数在一组数里面出现次数多于一半,那么这个数一定是唯一的,那么我们记录一下最多的数字出现的次数,然后用其他的数字来减小 c n t cnt cnt,这样最后得到的数字就是那个唯一众数
这有一个模拟这一过程的网页
https://www.cs.utexas.edu/~moore/best-ideas/mjrty/index.html

  • 这题跟DP似乎没什么关系,直接用哈希映射存储记录也可

3. hdu 1069 Monkey and Banana

  • 给你一些箱子的长宽高,可以任意摆放,也就是长、宽、高都可以作为高,箱子有无限个,每一层的箱子的平行于 x O y xOy xOy面的长和宽要严格大于上一层,以便于猴子往上爬,问最高能摆多高
  • 首先一个箱子最多有六种摆放方式,且每种摆放方式最多只能出现一次,因为要求下面严格大于上面,如果出现两次就会相等,所以我们可以记录所有的状态,首先把箱子大小排序,然后设 d p [ i ] dp[i] dp[i]为前 i i i个状态的箱子能够摆到的最高高度,这样 d p [ i ] = d p [ j ] + 第 i 个箱子的高度 , j ϵ [ 0 , i ) , x j > x i , y j > y i dp[i]=dp[j]+第i个箱子的高度,j\epsilon[0,i),x_j>x_i,y_j>y_i dp[i]=dp[j]+i个箱子的高度,jϵ[0,i),xj>xi,yj>yi
  • 这个解法实际上就是最长递减子序列的 O ( n 2 ) O(n^2) O(n2)方法
#include 

using namespace std;

typedef long long ll;

struct st{
  int x, y, z;
  bool operator < (const st &B)const{
    return x > B.x || (x == B.x && y > B.y);
  }
};
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int kase = 1;
  int n;
  while(cin >> n && n){
    vector<st> a;
    for(int i=0;i<n;i++){
      int x, y, z;
      cin >> x >> y >> z;
      a.push_back({x, y, z});
      a.push_back({x, z, y});
      a.push_back({y, x, z});
      a.push_back({y, z, x});
      a.push_back({z, x, y});
      a.push_back({z, y, x});
    }sort(a.begin(), a.end());
    int m = (int)a.size();
    vector<int> dp(m);
    int ans = 0;
    for(int i=0;i<m;i++){
      dp[i] = a[i].z;
      for(int j=0;j<i;j++){
        if(a[j].x > a[i].x && a[j].y > a[i].y) dp[i] = max(dp[i], dp[j] + a[i].z);
      }
      ans = max(ans, dp[i]);
    }
    cout << "Case " << kase++ << ": maximum height = " << ans << '\n';
  }
  return 0;
}

4. hdu 1074 Doing Homework

  • 每一个作业有一个截止日期和完成时间,超过截止日期的作业会被扣分,超过多少天扣多少分,找一个排列方式使得所有作业被扣分的总和最少,求最少扣分数和方案
  • n < 15 n<15 n<15,考虑状压 d p dp dp,记录每一个状态的前一个状态和前一个作业是谁,然后进行转移,因为要求如果结果相同那么字典序越小越好,所以考虑倒序枚举,尽量找靠前的合法位置
#include 

using namespace std;

typedef long long ll;

struct st{
  string name;
  int Deadline;
  int need;
};
struct answer{
  int fa;// 前一个状态是谁
  int score;// 当前扣分
  int now;// 做完的时间
  int who;// 现在是哪个作业
};
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int n;
    cin >> n;
    vector<st> a(n);
    for(int i=0;i<n;i++){
      cin >> a[i].name >> a[i].Deadline >> a[i].need;
    }
    vector<answer> dp(1 << n, {-1, 0x3f3f3f3f, 0, 0});
    dp[0].score = 0;
    int state = (1 << n) - 1;
    for(int s=1;s<=state;s++){
      for(int j=n-1;j>=0;j--){
        if((s >> j) & 1){
          int tmp = (s ^ (1 << j));
          int score = dp[tmp].now + a[j].need - a[j].Deadline;// 减少的分数
          if(score < 0) score = 0;// 按时完成了, 不扣分
          if(dp[s].score > dp[tmp].score + score){
            dp[s].who = j;
            dp[s].fa = tmp;
            dp[s].score = dp[tmp].score + score;
            dp[s].now = dp[tmp].now + a[j].need;            
          }
        }
      }
    }
    
    cout << dp[(1 << n) - 1].score << '\n';
    vector<string> ans(n);
    int j = n - 1;
    while(dp[state].fa != -1){
      ans[j] = a[dp[state].who].name;
      state = dp[state].fa;
      j -= 1;
    }
    for(int i=0;i<n;i++){
      cout << ans[i] << '\n';
    }
  }
  return 0;
}

5. hdu 1087 Super Jumping! Jumping! Jumping!

  • 水题
  • 显然是最长上升子序列,直接 O ( n 2 ) O(n^2) O(n2)即可,或者也可以单调队列优化到 O ( n l o g n ) O(nlogn) O(nlogn)
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  while(cin >> n && n){
    vector<int> a(n + 1);
    vector<ll> dp(n + 1);
    for(int i=1;i<=n;i++){
      cin >> a[i];
      dp[i] = a[i];
      for(int j=1;j<i;j++){
        if(a[i] > a[j]){
          dp[i] = max(dp[j] + a[i], dp[i]);
        }
      }
    }
    cout << *max_element(dp.begin(), dp.end()) << '\n';
  }
  return 0;
}

6. hdu 1114 Piggy-Bank

  • 问能把背包装满的最小装法
  • 完全背包,但是是变式问题,我们以往是求最大,但是现在改为了最小,但思路类似
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int e, f;
    cin >> e >> f;
    int n;
    cin >> n;
    vector<pair<int, int> > a(n + 1);
    for(int i=1;i<=n;i++){
      cin >> a[i].first >> a[i].second;
    }
    vector<int> dp(f - e + 1, 0x3f3f3f3f);
    dp[0] = 0;
    for(int i=1;i<=n;i++){
      for(int j=a[i].second;j<=f-e;j++){
        dp[j] = min(dp[j - a[i].second] + a[i].first, dp[j]);
      }
    }
    if(dp[f - e] == 0x3f3f3f3f){
      cout << "This is impossible.\n";
    }else{
      cout << "The minimum amount of money in the piggy-bank is " << dp[f - e] << ".\n";
    }
  }
  return 0;
}

7. hdu 1176 免费馅饼

  • 转移有两个方向,正着会出问题,因为终点对于转移会有影响,但是倒着就不会
#include 

using namespace std;

typedef long long ll;

const int N = 1e5;
int x[N + 10][11];

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  while(cin >> n && n){
    int mx = -1;
    for(int i=0;i<n;i++){
      int y, t;
      cin >> y >> t;
      mx = max(mx, t);
      x[t][y] += 1;
    }
    vector<vector<int> > dp(mx + 1, vector<int>(11));
    for(int i=mx-1;i>=0;i--){
      for(int j=0;j<11;j++){
        dp[i][j] = dp[i + 1][j] + x[i + 1][j];
        if(j + 1 < 11) dp[i][j] = max(dp[i][j], dp[i + 1][j + 1] + x[i + 1][j + 1]);
        if(j - 1 >= 0) dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + x[i + 1][j - 1]);
      }
    }
    for(int i=1;i<=mx;i++){
      memset(x[i], 0, sizeof x[i]);
    }
    cout << dp[0][5] << '\n';
  }
  return 0;
}

8. hdu 1260 Tickets

  • 想了个三维的 d p dp dp,设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示前 i i i个人,其中有 j j j对相邻的买票人, k k k表示第 i i i个人和 i − 1 i-1 i1个人是否一起买票了
#include 

using namespace std;

typedef long long ll;

const int INF=  0x3f3f3f3f;

const int N = 2000;
int dp[N + 5][N + 5][2];
int a[N + 5];
int b[N + 5];
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int k;
    memset(dp, 0x3f, sizeof dp);
    cin >> k;
    for(int i=1;i<=k;i++){
      cin >> a[i];
    }
    for(int i=2;i<=k;i++){
      cin >> b[i];
    }
    for(int i=1;i<=k;i++){
      for(int j=0;j<k;j++){
        if(j > 0){
          dp[i][j][0] = min(dp[i - 1][j][0] + a[i], dp[i - 1][j][1] + a[i]);
          if(i > 1) dp[i][j][1] = min(dp[i - 1][j - 1][0] - a[i - 1] + b[i], dp[i - 1][j][1] + a[i]);
        }else{
          dp[i][j][0] = (dp[i - 1][j][0] == INF ? 0 : dp[i - 1][j][0]) + a[i];
        }
      }
    }
    int ans = INF;
    for(int j=0;j<k;j++){
      for(int l=0;l<2;l++){
        ans = min(ans, dp[k][j][l]);
      }
    }
    if(ans == INF) ans = 0;
    int second = ans % 60;
    ans /= 60;
    int minute = ans % 60;
    ans /= 60;
    int hour = ans;
    cout << setw(2) << setfill('0') << 8 + hour << ':' << setw(2) << setfill('0')
     << minute << ':' << setw(2) << setfill('0') << second << ' ' << (8 + hour >= 12 ? "pm" : "am") << '\n'; 
  }
  return 0;
}
  • 但是 M L E MLE MLE了,实际上只需要一维 d p dp dp,设 d p [ i ] dp[i] dp[i]表示前 i i i个人花的钱数
#include 

using namespace std;

typedef long long ll;

const int INF=  0x3f3f3f3f;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int k;
    cin >> k;
    vector<int> a(k + 1);
    for(int i=1;i<=k;i++){
      cin >> a[i];
    }
    vector<int> b(k + 1);
    for(int i=2;i<=k;i++){
      cin >> b[i];
    }
    vector<int> dp(k + 1);
    for(int i=1;i<=k;i++){
      dp[i] = dp[i - 1] + a[i];
      if(i >= 2) dp[i] = min(dp[i - 2] + b[i], dp[i]);
    }
    int ans = dp[k];
    int second = ans % 60;
    ans /= 60;
    int minute = ans % 60;
    ans /= 60;
    int hour = ans;
    cout << setw(2) << setfill('0') << 8 + hour << ':' << setw(2) << setfill('0')
     << minute << ':' << setw(2) << setfill('0') << second << ' ' << (8 + hour >= 12 ? "pm" : "am") << '\n'; 
  }
  return 0;
}

9. hdu 1257 最少拦截系统

  • 容易发现问题可以转化为求一个最长上升子序列的长度
  • O ( n 2 ) O(n^2) O(n2)或者 O ( l o g n ) O(logn) O(logn)
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  while(cin >> n){
    vector<int> a(n + 1);
    for(int i=1;i<=n;i++){
      cin >> a[i];
    }
    vector<int> dp(n + 1, 1);
    int ans = 0;
    for(int i=1;i<=n;i++){
      for(int j=1;j<=i;j++){
        if(a[j] < a[i]){
          dp[i] = max(dp[j] + 1, dp[i]);
        }
      }
      ans = max(ans, dp[i]);
    }
    cout << ans << '\n';
  }

  return 0;
}
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  while(cin >> n){
    vector<int> a(n);
    for(int i=0;i<n;i++){
      cin >> a[i];
    }
    vector<int> LIS;
    for(int i=0;i<n;i++){
      int p = upper_bound(LIS.begin(), LIS.end(), a[i]) - LIS.begin();
      if(p == (int)LIS.size()){
        LIS.push_back(a[i]);
      }else{
        LIS[p] = a[i];
      }
    }
    cout << (int)LIS.size() << '\n';
  }
  return 0;
}

10. hdu 1160 FatMouse’s Speed

  • 类似最长上升子序列等问题, d p dp dp记录路径即可,注意输出编号
#include 

using namespace std;

typedef long long ll;

struct st{
  int w, s, fa;
  int sz;
};
struct node{
  int w, s, id;
};
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int w, s;
  vector<node> a(1);
  int n = 1;
  while(cin >> w >> s){
    a.push_back(node{w, s, n});
    n += 1;
  }
  sort(a.begin() + 1, a.end(), [&](node x, node y){
    return x.s > y.s;
  });
  n -= 1;
  vector<st> dp(n + 1);
  for(int i=1;i<=n;i++){
    dp[i].fa = i;
    dp[i].w = a[i].w;
    dp[i].s = a[i].s;
    dp[i].sz = 1;
    for(int j=1;j<i;j++){
      if(dp[i].sz < dp[j].sz + 1 && a[j].w < a[i].w && a[j].s > a[i].s){
        dp[i].fa = j;
        dp[i].w = a[j].w;
        dp[i].s = a[j].s;
        dp[i].sz = dp[j].sz + 1;
      }
    }
  }
  int mx = -1;
  int now = 0;
  for(int i=1;i<=n;i++){
    if(mx < dp[i].sz){
      mx = dp[i].sz;
      now = i;
    }
  }
  function<void(int)> solve = [&](int x){
    if(x == dp[x].fa){
      cout << a[x].id << '\n';
      return;
    }
    solve(dp[x].fa);
    cout << a[x].id << '\n';
  };
  cout << dp[now].sz << '\n';
  solve(now);
  return 0;
}

12. poj 1458 Common Subsequence

  • 最长公共子序列,设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 s 1 s_1 s1 i i i个字符和 s 2 s_2 s2 j j j个字符的最长公共子序列
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  string s1, s2;
  while(cin >> s1 >> s2){
    int n = s1.length();
    int m = s2.length();
    vector<vector<int> > dp(n + 1, vector<int>(m + 1));
    for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++){
        if(s1[i - 1] == s2[j - 1]){
          dp[i][j] = dp[i - 1][j - 1] + 1;
        }else{
          dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
        }
      }
    }
    cout << dp[n][m] << '\n';
  }
  return 0;
}

13. poj 1661 Help Jimmy

  • 从高处往低处走,掉落速度为 1 1 1,水平移动速度也是 1 1 1,问最短多长时间掉到地面
  • d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示从木板最左侧掉到下一块木板或地面需要的最短时间, d p [ i ] [ 1 ] dp[i][1] dp[i][1]表示从木板最右侧掉到下一块木板或地面所需的最短时间,那么我们考虑通过下一块木板的最左端和最右端这两个位置进行状态转移
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

struct st{
  int l, r, h;
  bool operator < (const st &B)const{
    return h > B.h;
  }
};

const int INF = 0x3f3f3f3f;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int n, x, y, mx;
    cin >> n >> x >> y >> mx;
    vector<st> a(n + 2);
    a[0].l = a[0].r = x; a[0].h = y;
    a[1].l = -20000; a[1].r = 20000; a[1].h = 0;
    vector<vector<int> > dp(n + 2, vector<int>(2));
    for(int i=2;i<=n+1;i++){
      cin >> a[i].l >> a[i].r >> a[i].h;
    }sort(a.begin(), a.end());
    for(int i=n;i>=0;i--){// 从下往上
      int k = i + 1;// 下面的某个
      bool ok = false;// 能不能通过 
      // 左边
      while(k < n + 1 && mx >= a[i].h - a[k].h){
        if(a[i].l >= a[k].l && a[i].l <= a[k].r){
          dp[i][0] = a[i].h - a[k].h + min(dp[k][0] + a[i].l - a[k].l, dp[k][1] + a[k].r - a[i].l);
          ok = true;
          break;
        }
        k += 1;
      }
      if(!ok){
        if(a[i].h - a[k].h > mx){
          dp[i][0] = INF;
        }else{
          dp[i][0] = a[i].h - a[k].h;
        }
      }
      // 右边
      ok = false;
      k = i + 1;
      while(k < n + 1 && mx >= a[i].h - a[k].h){
        if(a[i].r >= a[k].l && a[i].r <= a[k].r){
          dp[i][1] = a[i].h - a[k].h + min(dp[k][0] + a[i].r - a[k].l, dp[k][1] + a[k].r - a[i].r);
          ok = true;
          break;
        }
        k += 1;
      }
      if(!ok){
        if(a[i].h - a[k].h > mx){
          dp[i][1] = INF;
        }else{
          dp[i][1] = a[i].h - a[k].h;
        }
      }
    }
    cout << min(dp[0][0], dp[0][1]) << '\n';
  }
  return 0;
}

14. poj 2533 Longest Ordered Subsequence

  • 最长上升子序列
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<int> a(n + 1);
  vector<int> dp(n + 1, 1);
  for(int i=1;i<=n;i++) cin >> a[i];
  int ans = 0;
  for(int i=1;i<=n;i++){
    for(int j=1;j<i;j++){
      if(a[i] > a[j]){
        dp[i] = max(dp[j] + 1, dp[i]);
      }
    }
    ans = max(ans, dp[i]);
  }
  cout << ans << '\n';
  return 0;
}

15. poj 3186 Treats for the Cows

  • 每次从头或者从尾部选择一个元素,问所有选择方案中最大的情况是多少
  • 考虑 d p dp dp,设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前面选择 i i i个元素,后面选择 j j j个元素所能得到的最大值,容易列出转移方程
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<int> a(n + 1);
  for(int i=1;i<=n;i++){
    cin >> a[i];
  }
  vector<vector<int> > dp(n + 1, vector<int>(n + 1));
  for(int i=0;i<=n;i++){
    for(int j=0;i+j<=n;j++){
      int len = i + j;
      if(i > 0) dp[i][j] = max(dp[i][j], dp[i - 1][j] + len * a[i]);
      if(j > 0) dp[i][j] = max(dp[i][j], dp[i][j - 1] + len * a[n - (j - 1)]);
    }
  }
  int ans = 0;
  for(int i=0;i<=n;i++){
    ans = max(ans, dp[i][n - i]);
  }
  cout << ans << '\n';
  return 0;
}

16. hdu 1078 FatMouse and Cheese

  • 仔细理解题意,每次最多走最多k步,要么水平,要么垂直走,二选一,所以记忆化搜索,设dp[i][j]表示以(i,j)为终点所能得到的奶酪最多数
#include 

using namespace std;

int xx[] = {1, 0, -1, 0};
int yy[] = {0, 1, 0, -1};
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, k;
  while(cin >> n >> k) {
    if(n == -1 && k == -1) break;
    vector<vector<int> > mp(n, vector<int>(n)), dp(n, vector<int>(n));
    for(int i=0;i<n;i++) for(int j=0;j<n;j++) {
      cin >> mp[i][j];
    }
    function<int(int, int)> Dfs = [&](int x, int y) {
      if(dp[x][y]) return dp[x][y];
      int ans = 0;
      for(int i=0;i<4;i++) {
        for(int j=1;j<=k;j++) {
          int dx = x + xx[i] * j;
          int dy = y + yy[i] * j;
          if(dx >= 0 && dy >= 0 && dx < n && dy < n && mp[dx][dy] > mp[x][y]) {
            ans = max(ans, Dfs(dx, dy));
          }
        }
      }
      return dp[x][y] = ans + mp[x][y];
    };
    cout << Dfs(0, 0) << '\n';
  }
  return 0;
}

17. hdu 2859 Phalanx

  • 问以副对角线为对称轴的子矩阵最大是多大
  • 观察如何进行状态转移,设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示以 ( i , j ) (i,j) (i,j)为左下角的对称矩阵最大是多大,那么 d p [ i ] [ j ] dp[i][j] dp[i][j]可以从 d p [ i − 1 ] [ j + 1 ] dp[i-1][j+1] dp[i1][j+1]转移过来,为什么?因为如果 d p [ i − 1 ] [ j + 1 ] dp[i-1][j+1] dp[i1][j+1]已经计算好了, d p [ i ] [ j ] dp[i][j] dp[i][j]最大不会超过 1 + d p [ i − 1 ] [ j + 1 ] 1+dp[i-1][j+1] 1+dp[i1][j+1]
#include 

using namespace std;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  while(cin >> n && n){
    vector<string> mp(n);
    for(int i=0;i<n;i++) cin >> mp[i];
    vector<vector<int> > dp(n, vector<int>(n));// dp[i][j]表示以(i, j)为左下角的矩形的最大对称大小
    int ans = 1;// ans最少是1
    for(int i=0;i<n;i++){
      for(int j=0;j<n;j++){
        dp[i][j] = 1;
        if(i == 0 || j == n - 1) continue;
        int k = dp[i - 1][j + 1];// dp[i][j]可以从dp[i - 1][j + 1]转移过来, 最多能达到dp[i - 1][j + 1] + 1
        for(int s=1;s<=k;s++){
          if(mp[i - s][j] == mp[i][j + s]) {
            dp[i][j] += 1;
          }else {
            break;
          }
        }
        ans = max(ans, dp[i][j]);
      }
    }
    cout << ans << '\n';
  }
  return 0;
}

18. poj 3616 Treats for the Cows

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

struct st{
  int start;
  int end;
  int eff;
  bool operator < (const st &B)const{
    return start < B.start || (start == B.start && end < B.end);
  }
};
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m, r;
  cin >> n >> m >> r;
  vector<st> s(m + 1);
  for(int i=1;i<=m;i++){
    cin >> s[i].start >> s[i].end >> s[i].eff;
    s[i].end += r;
  }sort(s.begin() + 1, s.end());
  vector<int> dp(m + 1);
  for(int i=1;i<=m;i++){
    dp[i] = s[i].eff;
    for(int j=1;j<i;j++){
      if(s[i].start >= s[j].end){
        dp[i] = max(dp[i], dp[j] + s[i].eff);
      }
    }
  }
  int ans = 0;
  for(int i=1;i<=m;i++){
    ans = max(ans, dp[i]);
  }
  cout << ans;
  return 0;
}

19. poj 3666 Making the Grade

对于给定的 a [ i ] a[i] a[i],任意的数+1或者-1,使得 a [ i ] a[i] a[i]变成一个单调不增或者单调不减序列,问操作的最小次数

  • 容易想到的一点是最后那个序列里面的所有数一定是原来的序列里面某些数
  • d [ i ] [ j ] d[i][j] d[i][j]表示前 i i i个数,第 i i i个数是原来的第 j j j大的数时的操作次数, b [ i ] b[i] b[i]表示第 i i i大的数,那么有 d p [ i ] [ j ] = m i n { d p [ i − 1 ] [ k ] } 1 ≤ k ≤ n + ∣ a [ i ] − b [ i ] ∣ dp[i][j]=min\{dp[i-1][k]\}_{1\leq k\leq n}+|a[i]-b[i]| dp[i][j]=min{dp[i1][k]}1kn+a[i]b[i]
  • 上面是单调不减序列,对于单调不增序列,将 b [ i ] b[i] b[i]反过来即可
#include 
#include 

using namespace std;

const int N = 2e3 + 10;
int a[N], b[N], c[N];
int dp1[N][N];
int dp2[N][N];

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  for(int i=1;i<=n;i++) {
    cin >> a[i];
    b[i] = a[i];
  }sort(b + 1, b + n + 1);
  for(int i=1;i<=n;i++) {
    c[i] = b[n - i + 1];
    dp1[1][i] = abs(a[1] - b[i]);
    dp2[1][i] = abs(a[1] - c[i]);
  }
  for(int i=2;i<=n;i++) {
    int tmp1 = dp1[i - 1][1];
    int tmp2 = dp2[i - 1][1];
    for(int j=1;j<=n;j++) {
      tmp1 = min(tmp1, dp1[i - 1][j]);
      tmp2 = min(tmp2, dp2[i - 1][j]);
      dp1[i][j] = tmp1 + abs(a[i] - b[j]);
      dp2[i][j] = tmp2 + abs(a[i] - c[j]);
    }
  }
  int ans = 0x7fffffff;
  for(int i=1;i<=n;i++) {
    ans = min(ans, dp1[n][i]);
    ans = min(ans, dp2[n][i]);
  }
  cout << ans << '\n';
  return 0;
}

你可能感兴趣的:(#,专项训练,算法,动态规划)