kuangbin题单|基础dp

1.HDU1024 Max Sum Plus Plus 最大M子段和

dp[i][j]表示以第j个数结尾且被分为i段的最大子段和,答案即是dp[m][n]

与单集合(m=1)的最大字段和不同的点在于,本题需要组成m个不相交的最大子段,因而对状态转移方程进行修改:

  • 第一种情况:第j个数纳入前j1个数的最右最大子段中,即dp[i][j]=dp[i][j1]+a[j]

  • 第二种情况:第j个数不被纳入最右侧的最大子段,作为第i个子段存在,即dp[i][j]=dp[i1][k]+a[j],kj

综上:

dp[i][j]=max(dp[i][j1]+a[j],dp[i1][k]+a[j])

注意到范围n1e6,m虽然很小不会卡成O(n2),但依然会MLE,故考虑滚动数组优化:

dp[j]=max(dp[j1]+a[j],last[j1]+a[j])

其中last[j]表示前j个dp[j]的最大值,通过滚动数组维护并更新。

复杂度O(mnm2/2)

#include<cstdio>
#include<iostream>
#include<cstring>

const int maxn = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int n, m, a[maxn];
int f[maxn], last[maxn];
int tmp = -inf;

void dp() {
    for (int i = 1; i <= m; i++) {
        tmp = -inf;
        for (int j = i; j <= n; j++) {//j<i是无法分出i组子段的
            f[j] = std::max(f[j - 1] + a[j], last[j - 1] + a[j]);
            last[j - 1] = tmp;
            tmp = std::max(tmp, f[j]);
        }
    }
}

int main() {
    while (scanf("%d%d", &m, &n) == 2) {
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        memset(f, 0, sizeof(f));
        memset(last, 0, sizeof(last));
        dp();
        printf("%d\n", tmp);
    }
}

2.HDU1029 Ignatius and the Princess IV 计数?

map存一下出现次数就行,用不着dp

#include<iostream>
#include<cstdio>
#include<map>

std::map<int, int> m;
int n, x, ans;

int main() {
    while (scanf("%d", &n) == 1) {
        m.clear();
        for (int i = 1; i <= n; i++) {
            scanf("%d", &x);
            m[x]++;
            if (m[x] >= (n + 1) / 2) {
                ans = x;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

3.HDU1069 Monkey and Banana 三维偏序LIS+贪心

实质是x,y,z三维的最长上升子序列问题。对于每一个三维坐标(a,b,c),可以衍生6种堆叠方式,即:

x y z
a b c
a b c
b a c
b c a
c a b
c b a

考虑贪心策略,升序排列xy。剩下的就是O(n2)LIS的模板:令dp[i]表示以第i个数结尾的最长上升子序列,转移方程:

dp[i]=maxj.x<i.x,j.y<i.y(dp[i],dp[j]+a[i].z)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>

const int maxn = 30 + 10;
struct node {
    int siz[3];
} t[maxn];

struct node2 {
    int x, y, z;

    bool operator<(const node2 &p) const {
        return x == p.x ? y < p.y : x < p.x;
    }
} t2[maxn * 6];

int dp[maxn * 6];
int n, tot, T;

int f() {
    int ans = 0;
    for (int i = 1; i <= 6 * n; i++) {
        dp[i] = t2[i].z;
        for (int j = 1; j < i; j++) {
            if (t2[j].y < t2[i].y && t2[j].x < t2[i].x)
                dp[i] = std::max(dp[i], dp[j] + t2[i].z);
        }
        ans = std::max(ans, dp[i]);
    }
    return ans;
}

int main() {
    while (scanf("%d", &n) == 1 && n) {
        tot = 0;
        memset(t, 0, sizeof(t));
        memset(t2, 0, sizeof(t2));
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
            scanf("%d%d%d", &t[i].siz[0], &t[i].siz[1], &t[i].siz[2]);
        for (int i = 1; i <= n; i++) {
            t2[++tot] = (node2) {t[i].siz[0], t[i].siz[1], t[i].siz[2]};
            t2[++tot] = (node2) {t[i].siz[0], t[i].siz[2], t[i].siz[1]};
            t2[++tot] = (node2) {t[i].siz[1], t[i].siz[0], t[i].siz[2]};
            t2[++tot] = (node2) {t[i].siz[1], t[i].siz[2], t[i].siz[0]};
            t2[++tot] = (node2) {t[i].siz[2], t[i].siz[1], t[i].siz[0]};
            t2[++tot] = (node2) {t[i].siz[2], t[i].siz[0], t[i].siz[1]};
        }
        std::sort(t2, t2 + n * 6);
        int res = f();
        printf("Case %d: maximum height = %d\n", ++T, res);
    }
    return 0;
}

4.HDU1074 Doing Homework 状压dp

dp[i]表示作业状态为i时的最小超时。对于状态i,如果i中第j项作业是完成的,那么可以考虑转移状态:

dp[i]=min(dp[i],dp[i(1<<j)]+cost)

其中cost是完成该项作业的超时时间,如果不超时则cost0

因为题目要求字典序最小,而我们的dp转移过程是逆序的,故必须逆序枚举j以满足字典序要求。

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 15 + 5;
const int inf = 0x3f3f3f3f;
int dp[1 << maxn], cost[1 << maxn];
int head[1 << maxn];
int T, n;

struct node {
    int c, d;
    std::string s;
} t[maxn];

void out(int x) {
    if (!x) return;
    out(x - (1 << head[x]));
    std::cout << t[head[x]].s << std::endl;
}

int main() {
    scanf("%d", &T);
    while (T--) {
        memset(head, 0, sizeof(head));
        memset(cost, 0, sizeof(cost));
        scanf("%d", &n);
        for (int i = 0; i < n; i++)
            std::cin >> t[i].s >> t[i].d >> t[i].c;
        for (int i = 1; i < (1 << n); i++) {
            dp[i] = inf;
            for (int j = n - 1; j >= 0; j--) {
                if (!(i & (1 << j))) continue;
                int cos = std::max(0, cost[i - (1 << j)] + t[j].c - t[j].d);
                if (dp[i] > dp[i - (1 << j)] + cos) {
                    dp[i] = dp[i - (1 << j)] + cos;
                    cost[i] = cost[i - (1 << j)] + t[j].c;
                    head[i] = j;
                }
            }
        }
        printf("%d\n", dp[(1 << n) - 1]);
        out((1 << n) - 1);
    }
    return 0;
}

5.HDU1087 Super Jumping! Jumping! Jumping! 最大上升子序列和

dp[i]表示以a[i]结尾的最大上升子序列和

dp[i]=maxa[j]<a[i](dp[i],dp[j]+a[i])

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 1000 + 10;

int n, a[maxn];
int dp[maxn], res;

int main() {
    while (scanf("%d", &n) && n) {
        res = 0;
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for (int i = 1; i <= n; i++) {
            dp[i] = a[i];
            for (int j = 1; j < i; j++) {
                if (a[j] < a[i])
                    dp[i] = std::max(dp[i], dp[j] + a[i]);
            }
            res = std::max(res, dp[i]);
        }
        printf("%d\n", res);
    }
    return 0;
}

6.HDU1114 Piggy-Bank 完全背包

最小完全背包。令dp[i]表示重量为i时的最小钱币储存量,跑模板就是了。

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 1e4 + 10;
const int inf = 0x3f3f3f3f;
int T;
int E, F;
int n;
int w[maxn], c[maxn];

int dp[maxn];

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &E, &F);
        scanf("%d", &n);
        dp[0] = 0;
        for (int i = 1; i <= F - E; i++)
            dp[i] = inf;
        for (int i = 1; i <= n; i++)
            scanf("%d%d", &w[i], &c[i]);
        for (int i = 1; i <= n; i++)
            for (int j = c[i]; j <= F - E; j++)
                dp[j] = std::min(dp[j], dp[j - c[i]] + w[i]);
        if (dp[F - E] == inf) puts("This is impossible.");
        else printf("The minimum amount of money in the piggy-bank is %d.\n",]

7.HDU1176 免费馅饼 简单dp

dp[i][j]表示第i秒位于第j个位置上的最大价值,第j个位置可以由三个位置转移过来。

参考了一些方法防止下标越界,原本写的很抽象。

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 100000+10;
const int inf = 0x3f3f3f3f;
int n, x, T;
int dp[maxn][10 + 5];
int sum[maxn][10 + 5];
int maxT, res;

int main() {
    while (scanf("%d", &n) == 1 && n) {
        maxT = res = -inf;
        memset(sum, 0, sizeof(sum));
        memset(dp, -inf, sizeof(dp));
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &x, &T);
            sum[T][x + 1]++;
            maxT = std::max(maxT, T);
        }
        dp[0][5] = dp[0][6] = dp[0][7] = 0;
        for (int i = 1; i <= maxT; i++)
            for (int j = 1; j <= 11; j++)
                dp[i][j] = std::max(dp[i - 1][j] + sum[i][j],
                                    std::max(dp[i - 1][j - 1] + sum[i][j - 1], dp[i - 1][j + 1] + sum[i][j + 1]));
        for (int i = 1; i <= 11; i++)
            res = std::max(res, dp[maxT][i]);
        printf("%d\n", res);
    }
    return 0;
}

8.HDU1260 Tickets 简单dp

原本以为是区间dp,写一半发现区间只能从左到右转移,不是任意转移。

dp[i]表示前i个数的最小代价,可以发现对于第i个人要么单独买票,要么和第i1个人一起买票。

dp[i]=min(dp[i1]+a[i],dp[i2]+cost[i1])

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 2000 + 10;
const int inf = 0x3f3f3f3f;
int n;
int k, a[maxn], cost[maxn];
int dp[maxn];

int main() {
    scanf("%d", &n);
    while (n--) {
        memset(dp, inf, sizeof(dp));
        scanf("%d", &k);
        for (int i = 1; i <= k; i++) {
            scanf("%d", &a[i]);
        }
        for (int i = 1; i <= k - 1; i++) {
            scanf("%d", &cost[i]);
        }
        dp[0] = 0;
        dp[1] = a[1];
        for (int i = 2; i <= k; i++)
            dp[i] = std::min(dp[i - 1] + a[i], dp[i - 2] + cost[i - 1]);
        //printf("%d\n", dp[k]);
        int HH = (dp[k] / 3600 + 8) % 24, time = dp[k] % 3600;
        int MM = time / 60, SS = time % 60;
        if (HH >= 12) {
            printf("%02d:%02d:%02d pm\n", HH - 12, MM, SS);
        } else printf("%02d:%02d:%02d am\n", HH, MM, SS);
    }
    return 0;
}

9.HDU1257 最少拦截系统 最长上升子序列

Dilworth引理可知:最少的不上升子序列的个数就是最长上升子序列的长度。

然后就是O(nlogn)LIS模板了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

const int maxn = 1e5 + 7;
int n, a[maxn];
int dp[maxn];

int main() {
    while (~scanf("%d", &n)) {
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        dp[1] = a[1];
        int len = 1;
        for (int i = 2; i <= n; i++) {
            if (a[i] > dp[len]) dp[++len] = a[i];
            else *std::upper_bound(dp + 1, dp + 1 + len, a[i]) = a[i];
        }
        printf("%d\n", len);
    }
    return 0;
}

10.HDU1160 FatMouse's Speed 二维偏序LIS

对其中一维进行排序,对另一维求LIS即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

const int maxn = 1000 + 10;

int n, res, id;

struct node {
    int w, v;

    bool operator<(const node &p) const {
        return w == p.w ? v > p.v : w < p.w;
    }
} t[maxn];

int head[maxn], dp[maxn];

void out(int x) {
    if (!x || !head[x]) return;
    printf("%d\n", head[x]);
    out(head[x]);
}

int main() {
    while (scanf("%d%d", &t[++n].w, &t[n].v) != EOF);
    std::sort(t + 1, t + 1 + n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < i; j++) {
            if (t[j].w < t[i].w && t[j].v > t[i].v) {
                if (dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                    head[i] = j;
                }
            }
        }
        if (dp[i] > res) {
            res = dp[i];
            id = i;
        }
    }
    printf("%d\n", res);
    out(id);
}

11.POJ1015/UVA323 Jury Compromise 01背包+拔河问题

拔河问题见洛谷P1282。本题难度70%在路径输出上。

类似拔河问题,设dp[j][D][P]为前i个人选择了j个人的方案(bool类型)

那么显然,类似01背包可得出转移方程如下:

dp[j][D][P]=dp[j][D][P]|dp[j1][Dd[i]][Pp[i]]

不断更新min(|DP|),如果相等则更新max(D+P)

时间复杂度O(nm4002)

考虑优化(多米诺问题的分解方法),求最小|DP|下的最大D+P->求每一个|DP|下的最大D+P

dp[j][k]表示DP=k时的最大D+P和。枚举每一个k下的最大D+P和,那么转移方程变为:

dp[j][k]=max(dp[j][k],dp[j1][k(d[i]p[i])]+(d[i]+p[i])

时间复杂度O(nm400)

路径输出:

如果仅用二维数组head[j][k]记录每一个合法的i,那么显然会被覆盖。故增加一维,即head[i][j][k]

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 200 + 3;
const int inf = 0x3f3f3f3f;
int dp[20 + 5][20 * 20 * 2];//前i个人选择j个人,差为k的最大d+p和
int head[maxn][20 + 5][20 * 20 * 2];
int d[maxn], p[maxn];
int n, m, T;
int ans1, ans2;
int id[maxn], tot;
int base = 20 * 20;

void out(int i, int j, int k) {
    if (!j) return;
    int D = d[head[i][j][k + base]], P = p[head[i][j][k + base]];
    out(head[i][j][k + base] - 1, j - 1, k - (D - P));
    ans1 += D, ans2 += P;
    id[++tot] = head[i][j][k + base];
}

int main() {
    while (scanf("%d%d", &n, &m) == 2 && n && m) {
        memset(dp, -inf, sizeof(dp));
        memset(id, 0, sizeof(id));
        memset(head, 0, sizeof(head));
        tot = ans1 = ans2 = 0;
        for (int i = 1; i <= n; i++)
            scanf("%d%d", &d[i], &p[i]);
        //base = 20 * 20;
        dp[0][0 + base] = 0;

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++)
                for (int k = -20 * 20; k <= 20 * 20; k++)
                    head[i][j][k + base] = head[i - 1][j][k + base];
            for (int j = m; j; j--)
                for (int k = -20 * 20; k <= 20 * 20; k++) {
                    int temp = k - (d[i] - p[i]);
                    if (temp < -20 * 20 || temp > 20 * 20) continue;
                    if (dp[j][k + base] < dp[j - 1][temp + base] + d[i] + p[i]) {
                        dp[j][k + base] = dp[j - 1][temp + base] + d[i] + p[i];
                        head[i][j][k + base] = i;
                    }
                }
        }

        int res = 20 * 20;
        for (int i = -20 * 20; i <= 20 * 20; i++) {
            int temp = abs(i);
            if (dp[m][i + base] < 0) continue;
            if (temp < abs(res)) {//寻找最小差值k
                res = i;
            } else if (temp == abs(res)) {
                if (dp[m][i + base] > dp[m][res + base]) res = i;
            }
        }

        out(n, m, res);
        printf("Jury #%d\n", ++T);
        printf("Best jury has value %d for prosecution and value %d for defence:\n", ans1, ans2);
        for (int i = 1; i <= tot; i++) {
            printf(" %d", id[i]);
        }
        printf("\n\n");
    }
    return 0;
}

12.POJ1458 Common Subsequence 最长公共子序列

dp[i][j]表示两个序列对应位置分别为ij的最长公共子序列。

如果a[i]=b[j],那么dp[i][j]=dp[i1][j1]+1;否则取dp[i1][j]dp[i][j1]的最大值。

#include<iostream>
#include<cstdio>
#include<cstring>

const int maxn = 1000 + 10;
std::string a, b;

int dp[maxn][maxn];
int T;

int main() {
    while (std::cin >> a >> b) {
        int res = 0, ans = 0;
        memset(dp, 0, sizeof(dp));
        int len1 = a.length(), len2 = b.length();
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                int x = a[i - 1], y = b[j - 1];
                if (x == y) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        printf("%d\n", dp[len1][len2]);
    }
    return 0;
}

13.POJ1661 Help Jimmy 构造dp

好题目,参考此处

原问题涉及的变量很多,但我们发现,Jimmy的初始位置可以视为一个长度为0,高度为h的木板。同样,地面也可以视为一个长度为200002,高度为0的木板。故而dp的过程实质是在两两木板上转移,所以不妨设dp[i][0/1]Jimmy在第i个木板上,以左端点/右端点为起点的最短时间。

将n+1个木板按高度排序。对于第i个木板,寻找下一个高度差不超过max且垂直掉落的木板k。考虑i->k的转移:从第i块木板左端点/右端点移动到第k块木板左端点/右端点累计的时间即为垂直掉落的距离+横向移动的距离,这个是容易计算的。

以上的寻找过程只能遍历到第1块,第1块与第0块(地面)的转移需要特判。答案即为min(dp[n+1][0],dp[n+1][1])

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

const int maxn = 20000 + 10;
const int inf = 0x3f3f3f3f;
int n, max;

struct node {
    int x1, x2;
    int h;

    bool operator<(const node &p) const {
        return h < p.h;
    }
} t[maxn];

int dp[maxn][2];
int T;
int x, y;

void left(int i) {
    int k = i - 1;
    while (k > 0 && (t[i].h - t[k].h) <= max) {
        if (t[i].x1 >= t[k].x1 && t[i].x1 <= t[k].x2) {
            dp[i][0] = t[i].h - t[k].h + std::min(dp[k][0] + t[i].x1 - t[k].x1, dp[k][1] + t[k].x2 - t[i].x1);
            return;//掉到下一块板上并向左走/向右走
        } else k--;//当前板不在脚下
    }//处理1~i-1号木板
    if (t[i].h - t[k].h <= max) dp[i][0] = t[i].h - 0;//处理1号跳到0号木板(0号是地面)
    else dp[i][0] = inf;
}

void right(int i) {
    int k = i - 1;
    while (k > 0 && (t[i].h - t[k].h) <= max) {
        if (t[i].x2 >= t[k].x1 && t[i].x2 <= t[k].x2) {
            dp[i][1] = t[i].h - t[k].h + std::min(dp[k][0] + t[i].x2 - t[k].x1, dp[k][1] + t[k].x2 - t[i].x2);
            return;//掉到下一块板上并向左走/向右走
        } else k--;//当前板不在脚下
    }//处理2~i-1号木板
    if (t[i].h - t[k].h <= max) dp[i][1] = t[i].h - 0;
    else dp[i][1] = inf;
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d%d%d", &n, &x, &y, &max);
        for (int i = 1; i <= n; i++)
            scanf("%d%d%d", &t[i].x1, &t[i].x2, &t[i].h);
        t[n + 1] = (node) {x, x, y};
        t[0] = (node) {-20000, 20000, 0};
        std::sort(t, t + n + 1);
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n + 1; i++)
            left(i), right(i);
        printf("%d\n", std::min(dp[n + 1][0], dp[n + 1][1]));
    }
}

14.POJ2533 Longest Ordered Subsequence LIS

再裸不过的LIS了。

#include<iostream>
#include<cstdio>
#include<algorithm>

const int maxn = 1000 + 10;

int dp[maxn];
int a[maxn], len = 1, n;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    dp[1] = a[1];
    for (int i = 2; i <= n; i++) {
        if (a[i] > dp[len]) dp[++len] = a[i];
        else *std::upper_bound(dp + 1, dp + 1 + len, a[i]) = a[i];
    }
    printf("%d", len);
    return 0;
}

15.POJ3186/USACO06FEB Treats for the Cows 区间dp

dp[i][j]ij段的价值,可以发现,对于一个长度为len的子段,其寿命为(nlen+1)。那么转移方程如下:

dp[i][j]=max(dp[i+1][j]+(nlen+1)v[i],dp[i][j1]+(nlen+1)v[j])

初始化dp[i][i]=(n1+1)v[i]

#include<iostream>
#include<cstdio>

const int maxn = 2000 + 10;
int n, v[maxn];
int dp[maxn][maxn];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &v[i]);
    for (int i = 1; i <= n; i++)
        dp[i][i] = v[i] * n;
    for (int len = 2; len <= n; len++)
        for (int i = 1; i + len - 1 <= n; i++) {
            int j = i + len - 1;
            dp[i][j] = std::max(dp[i + 1][j] + (n - len + 1) * v[i], dp[i][j - 1] + (n - len + 1) * v[j]);
        }
    printf("%d", dp[1][n]);
    return 0;
}

16.HDU1078 FatMouse and Cheese 记忆化搜索

一开始写了个bfs然后MLE了。

一个巧妙的思路:枚举每一个k,在每一个k下跑记忆化搜索,即dp[x][y]=cheese[x][y]+res,res是从该点出发走j步(jk)的最大累计值。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>

const int maxn = 100 + 10;
const int dx[] = {-1, 0, 0, 1};
const int dy[] = {0, -1, 1, 0};

int n, k;
int cheese[maxn][maxn], ans;
int dp[maxn][maxn];
bool vis[maxn][maxn];

int dfs(int x, int y) {
    if (dp[x][y]) return dp[x][y];
    int res = 0;
    for (int i = 1; i <= k; i++)
        for (int j = 0; j < 4; j++) {
            int nx = x + dx[j] * i, ny = y + dy[j] * i;
            if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
            if (cheese[nx][ny] > cheese[x][y]) {
                res = std::max(res, dfs(nx, ny));
            }
        }
    return dp[x][y] = res + cheese[x][y];
}

int main() {
    while (scanf("%d%d", &n, &k) == 2 && n != -1 && k != -1) {
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                scanf("%d", &cheese[i][j]);
        memset(dp, 0, sizeof(dp));
        dfs(0, 0);
        printf("%d\n", dp[0][0]);
    }
    return 0;
}

17.HDU2859 Phalanx 最大对称子矩阵

dp[i][j]表示以ch[i][j]为左下角的最大对称子矩阵。初始化dp[1][i]=dp[i][n]=1

考虑(i1,j+1)>(i,j)的转移:设temp为以ch[i][j]为左下角,向上/右分别展开的匹配数。

dp[i][j]=min(dp[i][j]+1,temp)。为什么是取最小?因为对称子矩阵的要求使得dp[i][j]已经被dp[i1][j+1]限制住了,只能衍生最外侧。如

ctx
cpb
zcc

因为t的存在,故而即使temp=2,但也只能在dp[i1][j+1]+1

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;
const int maxn = 1000 + 10;
char ch[maxn][maxn];
int dp[maxn][maxn];
int n;

int main() {
    while (scanf("%d", &n) == 1 && n) {
        int ans = 1;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                cin >> ch[i][j];
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++) {
                if (i == 1 || j == n) {
                    dp[i][j] = 1;
                    continue;
                }
                int x = i, y = j;
                while (x >= 1 && y <= n && ch[x][j] == ch[i][y]) {
                    x--, y++;
                }
                int temp = i - x;
                dp[i][j] = temp > dp[i - 1][j + 1] + 1 ? dp[i - 1][j + 1] + 1 : temp;
                ans = std::max(ans, dp[i][j]);
            }
        printf("%d\n", ans);
    }
    return 0;
}

18.POJ3616/LuoguP2889 Milking Time S 区间覆盖

左端点排序,令dp[i]表示选择第i个区间的最大累计值

dp[i]=max(dp[i],dp[j]+w[i]|(j<i,l[i]r[j]>=r))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

const int maxn = 1e3 + 10;

struct node {
    int l, r, w;

    bool operator<(const node &p) const {
        return l < p.l;
    }
} t[maxn];

int n, m, r;
int dp[maxn], res;

int main() {
    scanf("%d%d%d", &n, &m, &r);
    for (int i = 1; i <= m; i++)
        scanf("%d%d%d", &t[i].l, &t[i].r, &t[i].w);
    std::sort(t + 1, t + 1 + m);
    for (int i = 1; i <= m; i++) {
        dp[i] = t[i].w;
        for (int j = 1; j < i; j++)
            if (t[i].l - t[j].r >= r)
                dp[i] = std::max(dp[i], dp[j] + t[i].w);
    }
    for (int i = 1; i <= m; i++)
        res = std::max(res, dp[i]);
    printf("%d", res);
    return 0;
}

19.[USACO08FEB] Making the GradeG 结论题/dp

本题是个广为人知的结论题,即存在一个O(nlogn)做法:维护一个大根堆,如果加入元素>堆顶则加入,否则将堆顶元素变为加入元素并计入代价。详细见@此处

然而本题范围并不严格,以下是O(n2)dp做法:

dp[i][j]表示已经完成前i个数,a[i]=b[j]的最小代价。其中,b数组是排序并离散化后的a数组。转移方程为:

dp[i][j]=minkj(dp[i1][k])+|a[i]b[j]|

其中的k可以通过滚动数组来维护,不需要枚举。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;
const int maxn = 2000 + 10;
int n, a[maxn];
int b[maxn], m;
int tmp, res = 0x3f3f3f3f;
int dp[maxn][maxn];//前i位,a[i]=b[j]的最小花费

void solve() {
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        tmp = dp[i - 1][0];
        for (int j = 1; j <= m; j++) {
            tmp = std::min(tmp, dp[i - 1][j]);//tmp为dp[i-1][k]的最小值(k \in [0,j])
            dp[i][j] = tmp + abs(a[i] - b[j]);
        }
    }
    for (int i = 1; i <= m; i++)
        res = std::min(res, dp[n][i]);
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]), b[i] = a[i];
    sort(b + 1, b + 1 + n);
    m = unique(b + 1, b + 1 + n) - b - 1;
    solve();
    reverse(a + 1, a + 1 + n);
    solve();
    printf("%d", res);
    return 0;
}
posted @   SxtoxA  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇
12 13
点击右上角即可分享
微信分享提示