【kuangbin】专题十二 基础DP1

【kuangbin】专题十二 基础DP1

https://vjudge.net/contest/68966#overview

前几周写了来着,忘更了

A - Max Sum Plus Plus

题意:把长为 \(n\) 的数列分为不相交的 \(m\) 块,问最大和为多少

分析:
朴素的 \(dp\) 式子:$f_{i,j}: $ 以 \(a_j\) 结尾的 \(i\) 个段的最大和
\(i\) 个数接到上一块后面去,\(f_{i,j}=f_{i,j-1}+a_i\)
\(i\) 个数新开一块,\(f_{i,j}=\)\(i\) 个数中构成 \(j-1\) 块的最大和 \(+a_i\)
\(f[i][j] = max (f[i][j-1]+a[j], max(f[i-1][k])+a[j]);\)
其中 \(k\) 表示在 \([i-1, j-1]\) 范围内最大的 \(f[i-1][k]\)

优化: 把max(dp[i-1][k])存起来

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 5;
int a[N], f[N]; //f[i-1]:max(dp[i-1][k])
int n, m, ans;

int main () {
    while (~scanf ("%d%d", &m, &n)) {
        memset (f, 0, sizeof f);
        for (int i = 1; i <= n; i ++)   cin >> a[i], a[i] += a[i-1];

        for (int i = 1; i <= m; i ++) {
            int tmp = a[i]; //dp[i-1][j]
            //for (int j = 1; j <= i; j ++)   tmp += a[j];
            ans = tmp; //前i项成i段

            for (int j = i + 1; j <= n; j ++) {
                tmp = max (tmp, f[j-1]) +  a[j] - a[j-1]; 
                //dp[i][j] = max (dp[i][j-1]+a[j], max(dp[i-1][k])+a[j]);
                f[j-1] = ans;
                ans = max (ans, tmp);
            }
        }
        cout << ans << endl;
    }
}

//m个最大字段和

//朴素的dp式子:
//dp[i][j]: 以a[j]结尾的,i个段的最大和
//dp[i][j] = max (dp[i][j-1]+a[j], max(dp[i-1][k])+a[j]);
//其中k表示在[i-1, j-1]范围内最大的dp[i-1][k]值

//优化:
//把max(dp[i-1][k])存起来

B - Ignatius and the Princess IV

输出出现次数大于长度一半的数,水题

#include <bits/stdc++.h>

using namespace std;
const int N = 1e7 + 5;
int n, a[N];

int main () {
    while (cin >> n) {
        for (int i = 0; i < n; i ++) {
            cin >> a[i];
        }
        sort (a, a + n);
        cout << a[n/2] << endl;
    }
}

C - Monkey and Banana

题意:有 \(n\) 种积木,每种无限量,可以任意翻转。问最多能把这些积木叠多高,叠的时候要满足**下一层的长和宽都必须大于上一层的长和宽。 **

分析:按题意背包

#include <bits/stdc++.h>

using namespace std;
const int N = 105;
int n, f[N];

struct Node {
    int a, b, c;
    void set (int x, int y, int z) {
        if (x > y)  swap (x, y);
        a = x, b = y, c = z;
    }
    bool operator < (const Node &t) const {
        if (a != t.a)   return a < t.a;
        if (b != t.b)   return b < t.b;
        return c < t.c;
    }
}e[N];

void solve () {
    for (int i = 1; i <= n; i ++) {
        int x, y, z;
        cin >> x >> y >> z;
        e[i].set(x, y, z);
        e[i+n].set(y, z, x);
        e[i+n*2].set(z, x, y);
    }
    sort (e + 1, e + 3*n + 1);
    
    f[1] = e[1].c;
    int ans = 0;
    for (int i = 1; i <= 3*n; i ++) {
        int maxn = 0;
        for (int j = 1; j < i; j ++) { //找出第i个之前的最大高度
            if (e[j].a < e[i].a && e[j].b < e[i].b) {
                maxn = max (maxn, f[j]);
            }
        }
        f[i] = e[i].c + maxn;
        ans = max (ans, f[i]);
    }
    cout << ans << endl;
}

int main () {
    int t = 1;
    while (cin >> n, n) {
        cout << "Case " << t << ": maximum height = ";
        solve ();
        t ++;
    }
}


//上一块选定为i了,那么下一块一定是叠在i的最佳高度上。

D - Doing Homework

题意:有 \(n\) 项作业,每项作业需要时间 \(c\) 去完成,截至日期是在 \(d\) ,每一项作业每超过截至时间就会扣1分,问如何安排作业能使得扣的分最少,扣分相同时,字典序最小。

#include <bits/stdc++.h>

using namespace std;
const int N = 20, M = (1<<15) + 5;
int n;
int f[M], tt[M], ans[M]; //扣分最小值,完成时间,最优时以何门作业最后完成

struct Node {
    string s;
    int ddl, dx;
}e[N];

void print_name (int x) {
    if (x == 0)     return ;
    print_name (x - (1 << (ans[x]-1)));
    cout << e[ans[x]].s << endl;
}

void solve () {
    cin >> n;
    int m = (1<<n) - 1;
    for (int i = 1; i <= n; i ++) {
        string s;
        int ddl, dx;
        cin >> s >> ddl >> dx;
        e[i] = {s, ddl, dx};
    }

    for (int i = 1; i <= m; i ++) {
        f[i] = 0x3f3f3f3f;
        for (int j = n; j >= 1; j --) { //倒叙保证字典序最小
            int st = (1<<(j-1)); //第 j 门在转态 i 的对应位置
            if ((i & st) == 0)  continue; //转态 i 中没有完成作业 j
            int cost = max (tt[i-st] + e[j].dx - e[j].ddl, 0); //以第 j 门作业最后完成时的将要扣的分

            if (f[i] > f[i-st] + cost) {
                f[i] = f[i-st] + cost;
                tt[i] = tt[i-st] + e[j].dx;
                ans[i] = j; ////转态 i 扣分最少时,是以何门作业最后完成
            }
        }
    }

    cout << f[m] << endl;
    print_name(m);
}

int main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}
    
//subject  ddl  耗时
//按ddl排序,靠前的排在前面,然后

//用dp[i] , 0 <= i<(1<<N) 代表每个状态的扣分的最小值
//对于每个状态i,枚举其每个已完成的作业j,
//然后求出以每个作业j为最后一门完成时的最小值

E - Super Jumping! Jumping! Jumping!

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1005;
int a[N], n;
ll f[N];

void solve () {
    memset (f, 0, sizeof f);
    ll ans = 0;
    for (int i = 1; i <= n; i ++)   cin >> a[i], f[i] = a[i];

    for (int i = 1; i <= n; i ++) {
        for (int j = i + 1; j <= n; j ++) {
            if (a[j] > a[i])
                f[j] = max (f[j], f[i] + a[j]);
        }
        ans = max (ans, f[i]);
    }

    cout << ans << endl;
}

int main () {
    while (cin >> n, n) {
        solve ();
    }
}

//最大上升子序列之和

F - Piggy-Bank

#include <bits/stdc++.h>

using namespace std;
const int N = 505, M = 10005;
int st, ed, n, m;
int v[N], w[N], f[M];

void solve () {
    memset (f, 0x3f, sizeof f);
    cin >> st >> ed >> n;
    m = ed - st;
    for (int i = 0; i < n; i ++)
        cin >> w[i] >> v[i];

    f[0] = 0;
    for (int i = 0; i < n; i ++) {
        for (int j = v[i]; j <= m; j ++) {
            f[j] = min (f[j], f[j-v[i]] + w[i]);
        }
    }
    if (f[m] == 0x3f3f3f3f) cout << "This is impossible.\n";
    else    cout << "The minimum amount of money in the piggy-bank is " << f[m] << "." << endl;
}

int main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}

//容量为m,完全背包
//能装满m的最小价值
//一定要放满

G - 免费馅饼

#include <bits/stdc++.h>

using namespace std;
const int N = 100005;
typedef pair<int, int> pii;
int n;
int f[N][15]; //t,x
int dx[] = {-1, 0, 1};

void solve () {
    memset (f, 0, sizeof f);
    int maxn = 0;
    for (int i = 0; i < n; i ++) {
        int x, t;
        scanf ("%d%d", &x, &t);
        f[t][x] ++;
        maxn = max (maxn, t);
    }

    //倒推
    for (int t = maxn-1; t >= 0; t --) {
        f[t][0] += max (f[t+1][0], f[t+1][1]);
        for (int x = 1; x < 10; x ++) {
            f[t][x] += max (f[t+1][x], max (f[t+1][x+1], f[t+1][x-1]));
        }
        f[t][10] += max (f[t+1][10], f[t+1][9]);
    }

    cout << f[0][5] << endl;
}

int main () {
    while (scanf ("%d", &n), n) {
        solve ();
    }
}

//0-10这11个位置
//初始在5

H - Tickets

#include<bits/stdc++.h>

using namespace std;
const int N = 2005;
int a[N], b[N], f[N], n;

void solve () {
    memset (f, 0x3f, sizeof f);
    cin >> n;
    for (int i = 1; i <= n; i ++)   cin >> a[i];
    for (int i = 1; i < n; i ++)    cin >> b[i];

    f[0] = 0, f[1] = a[1];
    for (int i = 2; i <= n; i ++) {
        f[i] = min (f[i-1] + a[i], f[i-2] + b[i-1]);
    }
    int ans = f[n];
    int h = ans / 3600, m = (ans-h*3600)/60, s = ans % 60;
    h += 8;
    printf("%02d:%02d:%02d ",h,m,s);
    if (h > 12)     cout << "pm\n";
    else    cout  << "am\n";
}

int main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}

//要么一个一个拿,要么两个一起拿

I - 最少拦截系统

#include <bits/stdc++.h>

using namespace std;
const int N = 5e4 + 5;
int n, a[N], f[N];

void solve () {
    //vector <int> a(n+1), f(n+1, 1);
    for (int i = 1; i <= n; i ++)   cin >> a[i];
    int ans = 1;

    f[1] = a[1];
    for (int i = 1; i <= n; i ++) {
        if (a[i] > f[ans])  f[++ans] = a[i];
        else {
            int pos = lower_bound (f+1, f+ans+1, a[i]) - f;
            f[pos] = a[i];
        }
    }
    cout << ans << endl;
}

int main () {
    while (cin >> n)
        solve ();
}

//最长上升子序列

J - FatMouse's Speed

#include <bits/stdc++.h>

using namespace std;
const int N = 10005;
int f[N], ans[N], n;

struct Node {
    int id, w, s;
    bool operator <(const Node &t) const {
        return w > t.w;
    }
}e[N];

int main () {
    int w, s;
    while (cin >> w >> s) {
        e[++ n] = {n, w, s};
    }
    sort (e + 1, e + n + 1);

    int len = 0, k = 1;
    f[1] = e[1].s, ans[1] = 1;
    for (int i = 1; i <= n; i ++) {
        if (f[k] < e[i].s) {
            f[++k] = e[i].s;
            ans[i] = k;
        }
        else {
            int pos = lower_bound (f, f+k+1, e[i].s) - f;
            f[pos] = e[i].s, ans[i] = pos;
        }
    }

    cout << k << endl;
    for (int i = n; i >= 1; i --) {
        if (k == ans[i]) {
            k --;
            cout << e[i].id << endl;
        }
    }
}


//重量增大时速度变小,问最多有多少只这样的老鼠

//根据重量排序然后找速度的:
//最长上升子序列 + 记录路径

K - Jury Compromise

$f[i][j][k]: $ 前 \(i\) 个人中选 \(j\) 个人,差值为 \(k=p_i-d_i\) 的方案中,\(p_i+d_i\) 最大的方案。考虑到 \(k\) 可能为负数,所以要加一个偏移量\(base\),把原范围 \([-400,400]\) 变为 \([0,800]\)

关键步骤:状态转移 -> 找最小差值 -> 输出路径

转移

不选:\(f[i][j][k]=f[i-1][j][k]\)
选:\(f[i][j][k]=f[i-1][j-1][k-(p_i-d_i)]+d_i+p_i\)
选的话必须先满足:\(j>0\)\(k\in [0,800]\)

找最小差值

最小差值 \(v\) 从0开始往上找,找到第一个满足 \(f[n][m][v+base] \geq 0\)\(f[n][m][base-v] \geq 0\)\(v\),注意二者都符合的话就找\(f\) 值更大的

记录路径

倒着推回去,如果 \(f\) 值不变,代表没入选,否则记录答案。
记得输出的时候再排下序

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#define endl "\n"

using namespace std;
const int N = 205, M = 25, base = 400;
int n, m;
int p[N], d[N], f[N][M][805], ans[N];

void solve () {
    memset (f, -0x3f, sizeof f);
    for (int i = 1; i <= n; i++)    cin >> p[i] >> d[i];
    
    //dp
    f[0][0][base] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            for (int k = 0; k <= 800; k++) {
                f[i][j][k] = f[i-1][j][k];
                if (j == 0)     continue;
                int dx = k - (p[i] - d[i]);
                if (dx >= 0 && dx <= 800) {
                    f[i][j][k] = max (f[i][j][k], f[i-1][j-1][dx] + p[i] + d[i]);
                }
            }
        }
    }

    //找最小差值
    int v = 0;
    while (f[n][m][v+base] < 0 && f[n][m][base-v] < 0)  v ++;
    if (f[n][m][v+base] >= f[n][m][base-v])     v = v + base;
    else    v = base - v;

    //找答案
    int k = 0;
    for (int i = n, j = m; i >= 1 && j >= 0; i--) {
        if (f[i][j][v] == f[i-1][j][v])     continue;
        ans[++ k] = i;
        v -= (p[i] - d[i]);
        j --;
    }

    int P = 0, D = 0;
    for (int i = 1; i <= k; i++) {
        P += p[ans[i]], D += d[ans[i]];
    }

    cout << "Best jury has value " << P << " for prosecution and value " << D << " for defence:\n";
    sort (ans + 1, ans + k + 1);
    for (int i = 1; i <= k; i++)    cout << ' ' << ans[i];
    cout << endl << endl;
}

int main () {
    ios::sync_with_stdio (0);cin.tie(0);cout.tie(0);
    int t = 1;
    while (cin >> n >> m, n || m) {
        cout << "Jury #" << t ++ << endl;
        solve ();
    }
}

L - Common Subsequence

#include <iostream>
#include <cstring>

using namespace std;
const int N = 1005;
string a, b;
int n, m;
int f[N][N];

int main () {
    while (cin >> a >> b) {
        memset (f, 0, sizeof f);
        n = a.size(), m = b.size();
        for (int i = 0; i < n; i ++)
            for (int j = 0; j < m; j ++) {
                if (a[i] == b[j])   f[i+1][j+1] = f[i][j] + 1;
                else    f[i+1][j+1] = max (f[i][j+1], f[i+1][j]);
            }
        cout << f[n][m] << endl;
    }
}



//最长公共子序列长度
//f[i][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);

M - Help Jimmy

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;
const int N = 1005, inf = 2e6;
int f[N][2]; // left/right
int n, x, y, maxh;

struct Node {
    int l, r, h;
    bool operator<(const Node &t) const {
        return h < t.h;
    }
}e[N];

void Left (int cur) { //求第m层向左走到地面的最少时间
    int nxt = cur - 1; //下一层
    while (nxt && e[cur].h - e[nxt].h <= maxh) { //没到地面且还能往下
        if (e[cur].l >= e[nxt].l && e[cur].l <= e[nxt].r) {
            int dx = min (e[cur].l - e[nxt].l + f[nxt][0], e[nxt].r - e[cur].l + f[nxt][1]);
            f[cur][0] = e[cur].h - e[nxt].h + dx;
            return ;
        }
        else    nxt --;
    }
    if (e[cur].h - e[nxt].h > maxh)     f[cur][0] = inf;
    else    f[cur][0] = e[cur].h; //可以跳到地面
}

void Right (int cur) { //求第m层向右走到地面的最少时间
    int nxt = cur - 1; //下一层
    while (nxt && e[cur].h - e[nxt].h <= maxh) { //没到地面且还能往下
        if (e[cur].r <= e[nxt].r && e[cur].r >= e[nxt].l) {
            int dx = min (e[cur].r - e[nxt].l + f[nxt][0], e[nxt].r - e[cur].r + f[nxt][1]);
            f[cur][1] = e[cur].h - e[nxt].h + dx;
            return ;
        }
        else    nxt --;
    }
    if (e[cur].h - e[nxt].h > maxh)     f[cur][1] = inf;
    else    f[cur][1] = e[cur].h; //可以跳到地面
}

void solve () {
    //memset (f, 0x3f, sizeof f);
    cin >> n >> x >> y >> maxh;
    e[n+1].l=x, e[n+1].r=x, e[n+1].h=y;
    e[0].l=-inf, e[0].r=inf, e[0].h=0;
   // e[n+1] = {x, x, y}, e[0] = {-inf, inf, 0};
    for (int i = 1; i <= n; i ++) {
        int l, r, h;
        cin >> e[i].l >> e[i].r >> e[i].h;
        //e[i] = {l, r, h};
    }
    sort (e, e + n + 2);
    for (int i = 1; i <= n+1; i ++) {
        Left(i), Right (i);
    }
    cout << min (f[n+1][0], f[n+1][1]) << endl;
}

int main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}

N - Longest Ordered Subsequence

#include <iostream>

using namespace std;
const int N = 1005;
int f[N], a[N], n, ans;

int main () {
    cin >> n;
    for (int i = 1; i <= n; i ++)   cin >> a[i];
    for (int i = 1; i <= n; i ++) {
        f[i] = 1;
        for (int j = 1; j < i; j ++) {
            if (a[i] > a[j])
                f[i] = max (f[i], f[j] + 1);
        }
        ans = max (f[i], ans);
    }
    cout << ans << endl;
}


//最长上升子序列长度

O - Treats for the Cows

#include <iostream>
#include <cmath>

using namespace std;
const int N = 2e3 + 5;
int f[N][N], a[N], n;

int main () {
    cin >> n;
    for (int i = 1; i <= n; i ++)   cin >> a[i];
    for (int i = 1; i <= n; i ++)   f[i][n+1] = f[i-1][n+1] + i * a[i];
    for (int i = n; i >= 1; i --)   f[0][i] = f[0][i+1] + (n - i + 1) * a[i];

    for (int i = 1; i <= n; i ++)
        for (int j = n; j > i; j --) {
            int k = i + (n - j + 1); //总共选了多少
            f[i][j] = max (f[i-1][j] + k * a[i], f[i][j+1] + k * a[j]);
        }

    int ans = 0;
    for (int i = 0; i <= n; i ++)   ans = max (ans, f[i][i+1]);
    cout << ans << endl;
}

//两端取

//dp[i][j]:前面取了a[0~i],后面取了a[j~n+1]的最大代价

P - FatMouse and Cheese

#include <bits/stdc++.h>

using namespace std;
const int N = 105;
int a[N][N], f[N][N], n, k;
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

bool Range (int x, int y) {
    if (x < 1 || x > n || y < 1 || y > n)
        return false;
    return true;
}

int dfs (int x, int y) {
    if (f[x][y])    return f[x][y];

    int maxn = a[x][y]; //至少能走自己这一个
    for (int i = 1; i <= k; i ++)
        for (int j = 0; j < 4; j ++) {
            int xx = x + dx[j] * i, yy = y + dy[j] * i;
            if (!Range(xx, yy) || a[xx][yy] <= a[x][y]) continue;
            maxn = max (maxn, dfs (xx, yy) + a[x][y]);
        }

    f[x][y] = maxn;
    return f[x][y];
}

void solve () {
    memset (f, 0, sizeof f);
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
            cin >> a[i][j];

    dfs (1, 1);
    cout << f[1][1] << endl;
}

int main () {
    while (cin >> n >> k, (n+1)&&(k+1)) {
        solve ();
    }
}


//每个格子有一个数a[i][j](范围在0~100)。每次只能横或竖直跳,且最多跳k格,跳的下一位数要比当前位大
//从(1,1)开始跳,问跳的数的和的最大值
//经典滑雪题

Q - Phalanx

#include <bits/stdc++.h>

using namespace std;
const int N = 1005;
char a[N][N];
int f[N][N], n, ans;

void solve () {
    memset (f, 0, sizeof f);
    ans = 0;
    //cin >> n;
    for (int i = 0; i < n; i ++)    cin >> a[i];
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++) {
            f[i][j] = 1;
        }

    for (int i = 0; i < n; i ++) {
        for (int j = n-1; j >= 0; j --) {
            int ii = i, jj = j;
            ii --, jj ++;
            while (ii >= 0 && jj < n && f[i][j] <= f[i-1][j+1] && a[ii][j] == a[i][jj]) {
                ii --, jj ++;
                f[i][j] ++;
            }
            ans = max (ans, f[i][j]);
        }

    }

    cout << ans << endl;
}

int main () {
    while (cin >> n, n) {
        solve ();
    }
}


//f[x][y]表示以(x,y)为左下角点的矩阵。
//对称矩阵f[i][d]是在对称矩阵f[i-1][d+1] 的基础上做出的

R - Milking Time

#include <iostream>
#include <algorithm>


using namespace std;
const int N = 1005;
int n, m, R;
int f[N];

struct Node {
    int l, r, w;
    bool operator <(const Node &t) const {
        return l < t.l;
    }
}e[N];


int main () {
    cin >> n >> m >> R;
    for (int i = 0; i < m; i ++) {
        cin >> e[i].l >> e[i].r >> e[i].w;
        e[i].r += R; //+休息时间
    }

    sort (e, e + m);
    int ans = 0;
    for (int i = 0; i < m; i ++) {
        f[i] = e[i].w;
        for (int j = 0; j < i; j ++) {
            if (e[j].r > e[i].l)    continue;
            f[i] = max (f[i], f[j] + e[i].w);
        }
        ans = max (ans, f[i]);
    }
    cout << ans << endl;
}

S - Making the Grade

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 2005;
int a[N], b[N], f[2][N];
int n;

int main () {
    cin >> n;
    for (int i = 0; i < n; i ++)    cin >> a[i], b[i] = a[i];
    sort (b, b + n);
    for (int i = 0; i < n; i ++)    f[0][i] = abs (a[0]-b[i]);
    for (int i = 1; i < n; i ++) {
        int lst = f[(i-1)&1][0];
        for (int j = 0; j < n; j ++) {
            lst = min (lst, f[(i-1)&1][j]);
            f[i&1][j] = abs (a[i] - b[j]) + lst;
        }
    }

    int ans = 1e9;
    for (int i = 0; i < n; i ++)    ans = min (ans, f[(n-1)&1][i]);
    cout << ans << endl;
}

//给定一个序列,以最小代价将其变为单调不增或单调不减序列

//dp[i][j]:当前处理到第i个数,j表示在b序列中排第j的数
//离散化 + dp + 滚动数组优化

dp 太太太太太太太太太难了😔

(没更完

posted @ 2022-08-19 12:06  Sakana~  阅读(65)  评论(2编辑  收藏  举报