动态规划の线性——摘花生,最低通行费,方格取数,传纸条,最长上升子序列の系列

摘花生

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;
}

你可能感兴趣的:(动态规划,算法)