大白书DP习题

来源:互联网 发布:南京大学知乎 编辑:程序博客网 时间:2024/04/29 05:39

9月7号~

开学啦,时间有点紧张。。

暑假算是白费啦~

今天开始刷大白书DP习题。。

一天一个!

废话不多说。。开刷!


Partitioning by Palindromes

第一天~

题目传送:UVA - 11584 - Partitioning by Palindromes

分类:DP入门题。

分析:因为是考虑回文串,很容易想到用两个指针来找以当前点为中点的回文串,每次找到一个回文串就进行更新,不过要注意偶数回文和奇数回文的情况

WA了两次,没注意到每次找到回文串都要更新还有刚开始需要更新一下。。

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;char s[1005];int dp[1005];//dp[i]表示以i为结尾的子串能够划分的最少回文串int main() {    int n;    s[0] = '$';    scanf("%d", &n);    while(n -- ) {        scanf("%s", s + 1);        int len = strlen(s + 1);        for(int i = 0; i <= len; i ++) dp[i] = i;        for(int i = 1; i <= len; i ++) {            int p = i - 1, q = i + 1;//左右两个指针            int l = 1;//s[i]为中点时的回文串的长度            dp[i] = min(dp[i], dp[p] + 1);//记得更新所有可能的情况            while(s[i] == s[q] && q <= len) {                l ++;                q ++;                dp[q - 1] = min(dp[q - 1], dp[p] + 1);//记得每走一步都要记得更新一下,不然只更新最后一步会错,比如bdbacabcb            }            while(p >= 0 && q <= len && s[p] == s[q]) {                l += 2;                p --;                q ++;                dp[q - 1] = min(dp[q - 1], dp[p] + 1);            }            dp[q - 1] = min(dp[q - 1], dp[p] + 1);        }        printf("%d\n", dp[len]);    }    return 0;}



Salesmen

第二天~

题目传送:UVALive - 4256 - Salesmen

分类:DP入门题。

分析:数据比较小,直接考虑暴力递推之。。

此题较顺利,,1A。

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0xfffffffusing namespace std;int n, n1, n2;int mp[105][105];int A[205];int dp[205][105];//dp[i][j]表示序列中第i个数字为j时需要修改的最少的数int main() {    int T;    scanf("%d", &T);    while(T --) {        memset(mp, 0, sizeof(mp));        scanf("%d %d", &n1, &n2);        int u, v;        for(int i = 0; i < n2; i ++) {            scanf("%d %d", &u, &v);            mp[u][v] = 1;            mp[v][u] = 1;        }        for(int i = 1; i <= n1; i ++) mp[i][i] = 1;        scanf("%d", &n);        for(int i = 1; i <= n; i ++) {            scanf("%d", &A[i]);        }        //初始化        for(int i = 0; i < 205; i ++) {            for(int j = 0; j < 105; j ++) {                dp[i][j] = INF;            }        }        for(int i = 1; i <= n1; i ++) {            if(i != A[1]) dp[1][i] = 1;            else dp[1][i] = 0;        }        //DP递推        for(int p = 2; p <= n; p ++) {//枚举序列的第几个位置            for(int i = 1; i <= n1; i ++) {//枚举该位置的每个可能取值                if(i != A[p]) {//此位置改变                    for(int j = 1; j <= n1; j ++) {                        if(mp[i][j] == 1) {                            dp[p][i] = min(dp[p][i], dp[p-1][j] + 1);                        }                    }                }                else if(i == A[p]) {                    for(int j = 1; j <= n1; j ++) {                        if(mp[i][j] == 1) {                            dp[p][i] = min(dp[p][i], dp[p-1][j]);                        }                    }                }            }        }        //for(int i = 1; i <= n; i ++) {        //  for(int j = 1; j <= n1; j ++) {        //      cout << dp[i][j] << " ";        //  }        //  cout << endl;        //}        int ans = INF;        for(int i = 1; i <= n1; i ++) {            ans = min(ans, dp[n][i]);        }        printf("%d\n", ans);    }    return 0;}



Wavio Sequence

第三天~

题目传送:UVA - 10534 - Wavio Sequence

分类:序列型DP(最长上升子序列)

分析:根据题目意思,可以知道只需要考虑每一个点作为中点时,维护往前的最长上升子序列,和往后的最长下降子序列即可,两个一前一后的子序列之间只要取较小的那个值就是此点的k+1,则更新答案ans = max(ans, 2* (k + 1) - 1)。1A。

其中最长上升子序列用了lower_bound,复杂度为O(n*logn)。

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;int n;int dp1[10005];//dp1[i]表示从1到i的i个数中的最长上升子序列int dp2[10005];//dp2[i]表示从i到n的n-i+1个数中的最长下降子序列int a[10005];int g[10005];//辅助数组int main() {    while(scanf("%d", &n) != EOF) {        for(int i = 1; i <= n; i ++) {            scanf("%d" , &a[i]);        }        for(int i = 1; i <= n; i ++) g[i] = INF;        for(int i = 1; i <= n; i ++) {            int k = lower_bound(g + 1, g + n + 1, a[i]) - g;            g[k] = a[i];            dp1[i] = k;        }        for(int i = 1; i <= n; i ++) g[i] = INF;        for(int i = n; i >= 1; i --) {            int k = lower_bound(g + 1, g + n + 1, a[i]) - g;            g[k] = a[i];            dp2[i] = k;        }        int ans = 0;        for(int i = 1; i <= n; i ++) {            int t = min(dp1[i], dp2[i]);            ans = max(ans, t * 2 - 1);        }        printf("%d\n", ans);    }    return 0;}



Fewest Flops

第四天~

题目传送:UVA - 11552 - Fewest Flops

分类:简单DP,重在设计状态以及状态怎么转移

分析:分成len/k个组就行了,然后定义dp[i][j]表示第i组以j结尾时的最小块数。然后递推即可,注意递推的时候要判断当前状态和前一状态是否存在这个字母,不然会出错,这里分别WA了一下。

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffff#define min(a, b) (a) < (b) ? (a) : (b)using namespace std;int k;char s[1005];int d[1005][26];//d[i][0~25]代表第i个组内是否存在a~z这些字母int cnt[1005];//cnt[i]代表第i组有多少个不同的字母int dp[1005][26];//d[i][0~25]代表第i个组以a~z结尾时的最小块数int main() {    int T;    scanf("%d", &T);    while(T --) {        memset(cnt, 0, sizeof(cnt));        memset(d, 0, sizeof(d));        scanf("%d %s", &k, s);        int len = strlen(s);        int zu = len / k;        int p = 0;        for(int i = 1; i <= zu; i ++) {            for(int j = 0; j < k; j ++, p ++) {                if(d[i][s[p] - 'a'] == 0) {                    cnt[i] ++;                    d[i][s[p] - 'a'] = 1;                }            }        }        //for(int i = 1; i <= zu; i ++) cout << cnt[i] << " "; cout << endl;        //边界        for(int i = 0; i <= zu; i ++) {            for(int j = 0; j < 26; j ++) {                dp[i][j] = INF;            }        }        for(int i = 0; i < 26; i ++) {            dp[1][i] = cnt[1];        }        //递推        for(int i = 2; i <= zu; i ++) {            for(int j = 0; j < 26; j ++) {//递推当前的26个字母                if(d[i][j] == 1) {//判断当前是否存在j这个字母                    for(int k = 0; k < 26; k ++) {//递推前一个状态的26个字母                        if(d[i-1][k]) {//判断前一个状态是否存在k这个字母                            if((d[i][k] == 1 && k != j) || (d[i][k] == 1 && k == j && cnt[i] == 1)) {                                dp[i][j] = min(dp[i][j], dp[i-1][k] + cnt[i] - 1);                            }                            else dp[i][j] = min(dp[i][j], dp[i-1][k] + cnt[i]);                        }                    }                }            }        }        int ans = INF;        //for(int i = 1; i <= zu; i ++) {        //  for(int j = 0; j < 26; j ++) {        //      cout << dp[i][j] << " ";        //  }        //  cout << endl;        //}        for(int i = 0; i < 26; i ++) {            ans = min(ans, dp[zu][i]);        }        printf("%d\n", ans);    }    return 0;}



Palindromic Subsequence

第五天~

题目传送:UVA - 11404 - Palindromic Subsequence

分类:序列型DP

分析:就是类似的LCS问题,如果输出长度就很简单,不过这里要输出回文串,想了半天不知怎么存,无奈搜了下题解,原来这里可以写在一个结构体里面来记录最小字典序,还要注意那个最小字典序可能不是回文串,需要奇偶分别判断一下输出

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;const int maxn = 1005;struct node {    int len;    string str;}dp[maxn][maxn];//类似LCS的dp,只不过加上了此时的最小字典序char s1[maxn];char s2[maxn];int n, len;int main() {    while(scanf("%s", s1 + 1) != EOF) {        len = strlen(s1 + 1);        s2[len + 1] = '\0';        for(int i = 1; i <= len; i ++) {            s2[len - i + 1] = s1[i];        }        //初始化        for(int i = 0; i <= len; i ++) {            dp[0][i].len = 0;            dp[0][i].str = "";        }        //printf("%s %s\n", s1 + 1, s2 + 1);        //递推        for(int i = 1; i <= len; i ++) {            for(int j = 1; j <= len; j ++) {                if(s1[i] == s2[j]) {                    dp[i][j].len = dp[i-1][j-1].len + 1;                    dp[i][j].str = dp[i-1][j-1].str + s1[i];                }                else {                    if(dp[i-1][j].len > dp[i][j-1].len) {                        dp[i][j].len = dp[i-1][j].len;                        dp[i][j].str = dp[i-1][j].str;                    }                    else if(dp[i][j-1].len > dp[i-1][j].len) {                        dp[i][j].len = dp[i][j-1].len;                        dp[i][j].str = dp[i][j-1].str;                    }                    else {                        dp[i][j].len = dp[i-1][j].len;                        dp[i][j].str = min(dp[i-1][j].str, dp[i][j-1].str);                    }                }            }        }        int ma = dp[len][len].len;        string ans = dp[len][len].str;        if(ma & 1) {            for(int i = 0; i < ma / 2; i ++) {                cout << ans[i];            }            for(int i = ma / 2; i >= 0; i --) {                cout << ans[i];            }            cout << endl;        }        else {            for(int i = 0; i < ma / 2; i ++) {                cout << ans[i];            }            for(int i = ma / 2 - 1; i >= 0; i --) {                cout << ans[i];            }            cout << endl;        }    }    return 0;}



Cellular Network

第六天~

有点累。。

题目传送:UVALive - 4731 - Cellular Network

分类:贪心+概率DP

分析:根据递推式可以很清楚的知道要将概率先从大到小排序,因为要尽可能小。
此外,设dp[i][j]表示前i个分成j组的最小期望值,递推即可

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;double dp[105][105];//dp[i][j]表示前i个分成j组的最小值double u[105];double p[105];double qzh[105];int n, w;bool cmp(double a, double b) {    return a > b;}int main() {    int T;    scanf("%d", &T);    while(T --) {        scanf("%d %d", &n, &w);        double sum = 0;        for(int i = 1; i <= n; i ++) {            scanf("%lf", &u[i]);            sum += u[i];        }        for(int i = 1; i <= n; i ++) {            p[i] = u[i] / sum;        }        sort(p + 1, p + n + 1, cmp);        //for(int i = 1; i <= n; i ++) cout << p[i] << " "; cout << endl;        for(int i = 1; i <= n; i ++) {            qzh[i] = qzh[i-1] + p[i];        }        for(int i = 1; i <= n; i ++) {//递推前i个            dp[i][1] = i * qzh[i];            for(int j = 2; j <= w && j <= i; j ++) {//分成j组                dp[i][j] = INF;                for(int k = j - 1; k < i; k ++) {//枚举前一状态k个分为j-1组时的情况                    dp[i][j] = min(dp[i][j], dp[k][j-1] + i * (qzh[i] - qzh[k]));                }            }        }        printf("%.4lf\n", dp[n][w]);    }    return 0;}



Mega Man’s Mission

第七天~

题目传送:UVA - 11795 - Mega Man’s Mission

分类:状态压缩DP

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;const int maxn = (1 << 16) + 5;int n;LL dp[maxn];//dp[i]表示状态为i时的顺序总数int kill[maxn];//kill[i]表示状态为i时可以消灭的机器人int wuqi[35];//武器属性int main() {    int T;    scanf("%d", &T);    for(int cas = 1; cas <= T; cas ++) {        memset(wuqi, 0, sizeof(wuqi));        char s[35];        scanf("%d", &n);        for(int i = 0; i <= n; i ++) {            scanf("%s", s);            wuqi[i] = 0;            for(int j = 0; s[j]; j ++) {                if(s[j] == '1') {                    wuqi[i] |= (1 << j);                }            }        }        int tot = (1 << n) - 1;        kill[0] = wuqi[0];        for(int s = 1; s <= tot; s ++) {            kill[s] = wuqi[0];            for(int i = 1; i <= n; i ++) {                if(s & (1 << (i - 1))) kill[s] |= wuqi[i];            }        }        memset(dp, 0, sizeof(dp));        dp[0] = 1;//一个机器人都不杀的方案为1        for(int i = 1; i <= tot; i ++) {//枚举全集            for(int j = 1; j <= n; j ++) {//枚举当前集合去掉第j个机器人的子集                if(i & (1 << (j -1))) {                    int zi = i ^ (1 << (j - 1));                    if(kill[zi] & (1 << (j - 1))) {                        dp[i] += dp[zi];                    }                }            }        }        //for(int i = 0; i <= tot; i ++) {        //  printf("%d %d %d\n", i, dp[i], wuqi[i]);        //}        printf("Case %d: %lld\n", cas, dp[tot]);    }    return 0;}



Jump

第八天~

有点破事,,隔了一天才弄

题目分类:变形的Joseph问题

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;const int maxn = 500005;int f[maxn];int a[5];int n, k;int main() {    int T;    scanf("%d", &T);    while(T --) {        scanf("%d %d", &n, &k);        for(int i = 1; i <= 3; i ++) {            f[i] = (k - 1) % i;            for(int j = i + 1; j <= n; j ++) f[j] = (f[j - 1] + k) % j;            a[i] = (1 + f[n]) % n;            if(a[i] <= 0) a[i] += n;        }        printf("%d %d %d\n", a[3], a[2], a[1]);    }    return 0;}



Martian Mining

第九天~

题目传送:UVALive - 3530 - Martian Mining

有点累了,直接上代码吧。。

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;const int maxn = 505;int n, m;int A[maxn][maxn];int B[maxn][maxn];int dpa[maxn][maxn];//dpa[i][j]表示以i,j为右下角,1,1为左上角所围成的矩形里,在i,j这个点运输A可以得到的最大值int dpb[maxn][maxn];//dpb[i][j]表示以i,j为右下角,1,1为左上角所围成的矩形里,在i,j这个点运输B可以得到的最大值int sum_row[maxn][maxn];int sum_col[maxn][maxn];int main() {    while(scanf("%d %d", &n, &m) != EOF) {        if(n == 0 && m == 0) break;        memset(dpa, 0, sizeof(dpa));        memset(dpb, 0, sizeof(dpb));        for(int i = 1; i <= n; i ++) {            for(int j = 1; j <= m; j ++) {                scanf("%d", &A[i][j]);                sum_row[i][j] = sum_row[i][j-1] + A[i][j];            }        }        for(int i = 1; i <= n; i ++) {            for(int j = 1; j <= m; j ++) {                scanf("%d", &B[i][j]);                sum_col[i][j] = sum_col[i-1][j] + B[i][j];            }        }        for(int i = 1; i <= n; i ++) {            for(int j = 1; j <= m; j ++) {                dpa[i][j] = max(dpa[i-1][j], dpb[i-1][j]) + sum_row[i][j];                dpb[i][j] = max(dpa[i][j-1], dpb[i][j-1]) + sum_col[i][j];            }        }        printf("%d\n", max(dpa[n][m], dpb[n][m]));    }    return 0;}



Paths through the Hourglass

第十天~

题目传送:UVA - 10564 - Paths through the Hourglass

啊啊啊啊啊,,不行了啊,,感觉做不动了,,虽然这个还是比较水的题。。因为没有注意到有个地方要反过来,,萎了好久好久。。。

分类:类似01背包的题

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;const int maxn = 385;int n, S;int a[45][25];LL dp[45][25][maxn];//dp[i][j][k]的值表示在位置i,j的时候,选了位于i,j的数时有多少路径上的数之和等于k(这里是逆推)int main() {    while(scanf("%d %d", &n, &S) != EOF) {        if(n == 0 && S == 0) break;        for(int i = 1; i <= n; i ++) {            for(int j = 1; j <= n - i + 1; j ++) {                scanf("%d", &a[i][j]);            }        }        for(int i = n + 1; i <= 2 * n - 1; i ++) {            for(int j = 1; j <= i - n + 1; j ++) {                scanf("%d", &a[i][j]);            }        }        if(S >= 360) {            printf("0\n\n");            continue;        }        //因为要打印路径,,所以采用逆推比较好        memset(dp, 0, sizeof(dp));        for(int i = 1; i <= n; i ++) {            dp[2 * n - 1][i][a[2 * n - 1][i]] = 1;        }        for(int i = 2 * n - 2; i >= n; i --) {            for(int j = 1; j <= i - n + 1; j ++) {                for(int k = S; k >= 0; k --) {                    dp[i][j][k + a[i][j]] += dp[i + 1][j][k] + dp[i + 1][j + 1][k];                }            }        }        for(int i = n - 1; i >= 1; i --) {            for(int j = 1; j <= n - i + 1; j ++) {                for(int k = S; k >= 0; k --) {                    dp[i][j][k + a[i][j]] += dp[i + 1][j - 1][k] + dp[i + 1][j][k];                }            }        }        LL cnt = 0;        for(int i = 1; i <= n; i ++) {            cnt += dp[1][i][S];        }        cout << cnt << endl;        if(cnt == 0) {            cout << endl;            continue;        }        int p;        for(int i = 1; i <= n; i ++) {            if(dp[1][i][S] >= 1) {                p = i;                break;            }        }        printf("%d ", p - 1);        int prev = S;//打印路径        for(int i = 2; i <= n; i ++) {            if(dp[i][p-1][S-a[i-1][p]]) {                cout << 'L';                S -= a[i-1][p];                p = p - 1;            }            else {                cout << 'R';                S -= a[i-1][p];            }        }        for(int i = n + 1; i <= 2 * n - 1; i ++) {            if(dp[i][p][S-a[i-1][p]]) {                cout << 'L';                S -= a[i-1][p];            }            else {                cout << 'R';                S -= a[i-1][p];                p = p + 1;            }        }        cout << endl;    }    return 0;}



Strategic game

第11天~

题目传送:UVALive - 2038 - Strategic game

题目分类:树形DP

题目分析:

基础的树形DP(最小顶点覆盖)

首先定义:

  • dp[i][0]代表以i为根节点的子树,不选根节点时所需要的最小点数。
  • dp[i][1]代表以i为根节点的子树,选根节点时所需要的最小点数。

先考虑不选根节点时,因为要覆盖所有边,所以他的子节点都要选。
再考虑选了根节点时,此时,他的子节点可选可不选,取较小的那个即可。

则有状态转移方程:

  • dp[u][0] = dp[v][1] (v为u的子节点)
  • dp[u][1] = min(dp[v][0], dp[v][1]) (v为u的子节点)

AC代码:

#include <map>#include <set>#include <list>#include <cmath>#include <deque>#include <queue>#include <stack>#include <bitset>#include <cctype>#include <cstdio>#include <string>#include <vector>#include <complex>#include <cstdlib>#include <cstring>#include <fstream>#include <sstream>#include <utility>#include <iostream>#include <algorithm>#include <functional>#define LL long long#define INF 0x7fffffffusing namespace std;const int maxn = 1505;int n;vector<int> G[maxn];int dp[maxn][2];//dp[i][0]表示以i为根的子树的根节点不选的情况,dp[i][1]表示以i为根的子树的根节点选了的情况void dfs(int u, int fa) {    dp[u][0] = 0;    dp[u][1] = 1;    int d = G[u].size();    for(int i = 0; i < d; i ++) {        int v = G[u][i];        if(v != fa) {            dfs(v, u);        }    }    for(int i = 0; i < d; i ++) {        int v = G[u][i];        if(v != fa) {            dp[u][0] += dp[v][1];            dp[u][1] += min(dp[v][0], dp[v][1]);        }    }}int main() {    while(scanf("%d", &n) != EOF) {        for(int i = 0; i <= n; i ++) {            G[i].clear();        }        int u, v, t;        for(int i = 0; i < n; i ++) {            scanf("%d:(%d)", &u, &t);            for(int i = 1; i <= t; i ++) {                scanf("%d", &v);                G[u].push_back(v);                G[v].push_back(u);            }        }        memset(dp, 0, sizeof(dp));        dfs(0, -1);        printf("%d\n", min(dp[0][0], dp[0][1]));    }    return 0;}



0 0
原创粉丝点击