NowCoder Winter Camp Contest II 题解

比赛链接:https://www.nowcoder.net/acm/contest/74#question
比赛时间:2018.1.28 14:00 ~ 17:00
比赛内容:DP+C语言水题。其中dp考察了01背包问题,区间dp-最优三角剖分,状压dp,树的重心等比较经典的DP问题,由于个人对DP的理解不多,又正值放假,拖到一周后才补完题,并写了这篇题解。

A 吐泡泡
题目描述

给定一段由o和O组成的字符串,两个相邻的小o会变成一个大O,两个相邻的大O会变成一个空串。输出经过这种变化之后的串。

思路

C++ string有一个replace函数,用法如下:

//用str替换指定字符串从起始位置pos开始长度为len的字符 
string& replace (size_t pos, size_t len, const string& str); 

顺序遍历一遍,对不同情况调用replace函数处理,注意处理完之后当前指针的变化。

代码
#include <bits/stdc++.h>
using namespace std;

string ans;
int main() {
    while (cin >> ans) {
        for (int i = 1; i < ans.size(); ) {
            if (i > 0 && ans[i] == 'o' && ans[i-1] == 'o') {
                ans = ans.replace(i-1, 2, "O");
                i--;
            } else if (i > 0 && ans[i] == 'O' && ans[i-1] == 'O') {
                ans = ans.replace(i-1, 2, "");
                i--;
            } else
                i++;
        }
        cout << ans << endl;
    }
    return 0;
}
B TaoTao要吃鸡
题目描述

01背包问题,如果背包没有装满那么就可以装进一个任意重量的物品,问最大价值。

思路

比赛的时候我的思路是:求得m-1背包容量的最大价值,然后同时知道这种最大价值下装了哪些物品,那么此时所能获得的最大价值就是在剩下的物品中挑选价值最大的那个。用used[i, j]来表示背包容量为j时是否用了第i个物品,如果used[i, j] 为true,就跳到used[i-1, j-w[i]]状态,否则就跳转至used[i-1, j]状态。
后来看了别人的AC代码,发现还可以这样做。由于背包未满时可以任意挑选一件还未装的物品,那么所能获得的最大价值就是我不用这个物品去填充m-1背包所能所得的最大价值加上该物品的最大价值。因此就用一个三重循环来解决。
而我只用了一个二重循环,但是开了一定的空间。

代码
#include <bits/stdc++.h>
using namespace std;
int n, m, h;
int dp[205], w[105], v[105];
bool used[105][205];
int main() {
    //freopen("/Users/leey/Documents/cppProjects/input.in", "r", stdin);
    scanf("%d", &n);
    while (n) {
        memset(dp, 0, sizeof dp);
        memset(used, false, sizeof used);

        scanf("%d %d", &m, &h);
        for (int i = 1; i <= n; i++) {
            scanf("%d %d", &w[i], &v[i]);
        }

        m += h;

        for (int i = 1; i <= n; i++) {
            for (int j = m; j >= w[i]; j--) {
                if (dp[j-w[i]]+v[i] > dp[j]) {
                    dp[j] = dp[j-w[i]]+v[i];
                    used[i][j] = true;
                }
            }
        }

        if (h == 0) {
            printf("%d\n", dp[m]);
        } else {
            int j = m-1, res = 0;
            for (int i = n; i >= 1; i--) {
                if (used[i][j]) {
                    j -= w[i];
                } else {
                    res = max(res, v[i]);
                }
            }
            printf("%d\n", res + dp[m-1]);
        }
        scanf("%d", &n);
    }
    return 0;
}
总结

01背包问题的变种。需要加深对01背包问题的理解。

C 小仙女过生日啦
题目描述

给定一个n多边形,按照对角线切成n-3个三角形,问你最大的三角形面积最小是多少。

思路

区间dp-最优三角剖分问题。
具体的dp思路下面的两个blog链接介绍的很清楚了:
https://www.cnblogs.com/Jason-Damon/p/3298172.html
https://www.cnblogs.com/Konjakmoyu/p/4905563.html

代码

结合代码上的注释理解一下dp思想。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 105;
struct Node {
    double x, y;
}node[maxn];
double dp[maxn][maxn];
int n;

//根据三点坐标计算三角形面积,其中a,b,c按照逆时针排序
double mul(int a, int b, int c) {
    return 0.5*fabs((node[a].x - node[c].x)*(node[b].y-node[c].y)
                    -(node[b].x-node[c].x)*(node[a].y-node[c].y));
}

//计算几何知识->判断某一点是否在三角形内部:点和三角形各边构成的三角形的面积==原三角形面积
//这样的划分才算正确的划分。
bool check(int a, int b, int c) {
    double area = mul(a, b, c);
    double ra, rb, rc;
    for (int i = 1; i <= n; i++) {
        if (i==a || i==b || i==c) continue;
        ra = mul(i, b, c), rb = mul(a, i, c), rc = mul(a, b, i);
        if (fabs(area - ra - rb - rc) < 1e-8) return 0;
    }
    return 1;
}

//区间dp-按照长度递增的顺序来进行处理,本题中状态dp[i, j]指多边形i..j的划分的最大三角形的最小面积。
int main() {
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; i++) {
            scanf("%lf %lf", &node[i].x, &node[i].y);
        }
        for (int d = 3; d <= n; d++) {
            for (int i = 1; i+d-1<=n; i++) {
                if (d == 3) {
                    dp[i][i+2] = mul(i, i+1, i+2);
                    continue;
                }
                int j = i+d-1;
                dp[i][j] = 0x3f3f3f3f;
                for (int k = i+1; k < j; k++) {
                    if (check(i, j, k))
                        dp[i][j] = min(dp[i][j], 
                                       max(max(dp[i][k], dp[k][j]), mul(i,j,k)));
                }
            }
        }
        printf("%.1f\n", dp[1][n]);
    }
    return 0;
}
D YB要打炉石
题目描述

最长递增子序列。直接O(n2)解决就可以,dp[i]表示以第i个字符结尾的最长递增子序列的长度。

E 小G有一颗大树
题目描述

树的重心(n个结点的无根树,选择一个结点为根,树的重心是这样的一个结点,其最大的子树的结点数最小,或者这样说,删除这个点所形成的图中的最大连通块中的结点数最少。

思路

这题的主要思想 是 记忆化搜索,也就是动态规划的别名。其实主要的话还是dfs,不断的搜索子节点,同时更新son数组(代表某一节点的子节点的个数),然后在回溯的过程中将子节点的son值加到父亲节点上。

代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int son[N], vis[N];
vector<int> g[N];
int n, ans, size;

void dfs(int curr) {
    vis[curr] = 1;
    son[curr] = 0;
    int tmp = 0;
    for (int i = 0; i < g[curr].size(); i++) {
        if (vis[g[curr][i]]) continue;
        dfs(g[curr][i]);
        son[curr] += son[g[curr][i]] + 1;
        //记忆化搜索就体现在此处的son[g[curr][i]]已经在之前的搜索过程中存储了起来
        tmp = max(tmp, son[g[curr][i]] + 1);
    }
    tmp = max(tmp, n - 1 - son[curr]);
    if (tmp < size || (tmp == size && curr < ans)) {
        size = tmp;
        ans = curr;
    }
}


int main() {
    //freopen("H://input.txt", "r", stdin);
    while (scanf("%d", &n) != EOF) {
        memset(son, 0, sizeof son);
        memset(vis, 0, sizeof vis);
        size = INT_MAX;
        ans = 0;
        for (int i = 0; i < N; i++)
            g[i].clear();

        for (int i = 1; i < n; i++) {
            int u, v;
            scanf("%d %d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }

        //select a root
        dfs(1);

        printf("%d %d\n", ans, size);
    }
    return 0;
}
F 德玛西亚万岁
题目描述

mxn的矩阵由0,1构成,其中可以在1的位置放置一枚棋子,要求棋子的周围不能有其余棋子,问一共有多少种放置方法。

思路

状压dp-状态压缩dp,常用于棋盘、矩阵,且每一个单元格只有两种状态的情况下。为什么叫状态压缩呢?因为这种情况下状态往往非常多,往往需要多维状态来表示。而我们往往用二进制来表示一行的状态,比如00011这个状态我们就用3来表示。状态间的操作往往需要用位操作来实现。

代码
#include <bits/stdc++.h>
using namespace std;

const int mod = 100000000;
const int N = 13;
const int M = 1 << N;

long long dp[N][M]; //dp[i, j],第i行状态为j时所对应的种树
int mark[M], land[M]; // mark数组,用来标记合法的状态,land数组,表示给定的状态

//状态x是否有相邻的1,通过(x & (x << 1))来实现
bool judge(int x) {
    if ((x & (x << 1)) != 0) return false;
    return true;
}

//判断第i行第j状态是否合法,mark[j]代表没有相邻的1的状态,land[i]代表题目给定的合法状态
bool judge(int i, int j) {
    return (land[i] & mark[j]) == mark[j];
}

int main() {
    //freopen("H://input.txt", "r", stdin);
    int n, m, x;
    while (scanf("%d %d", &n, &m) != EOF) {
        memset(dp, 0, sizeof dp);
        memset(mark, 0, sizeof mark);
        memset(land, 0, sizeof land);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                scanf("%d", &x);
                if (x == 1)
                    land[i] += (1 << (m - 1 - j));
            }
        }


        //初始化状态,求取出没有相邻1的合法状态
        int len = 0;
        for (int i = 0; i < (1 << m); i++) {
            if (judge(i)) {
                mark[len++] = i;
            }
        }

        for (int i = 0; i < len; i++) {
            if (judge(0, i)){
                dp[0][i] = 1;
            }
        }

        for (int i = 1; i < n; i++) {
            for (int j = 0; j < len; j++) {
            //检查第i行第j状态是否合法
                if (!judge(i, j)) continue;
                for (int k = 0; k < len; k++) {
                    //检查第i-1行状态k是否合法
                    if (!judge(i - 1, k)) continue; 
                    //检查第i行状态j和第i-1行状态j之间是否有相邻的1(竖着
                    if (mark[j] & mark[k]) continue;
                    dp[i][j] += dp[i - 1][k];

                }
            }
        }

        long long ans = 0;
        for (int i = 0; i < len; i++) {
            ans += dp[n-1][i];
            if (ans >= mod)
                ans %= mod;
        }
        printf("%lld\n", ans);

    }
    return 0;
}
G 送分了
题目描述

给定一个区间[m, n],问你其中包含38或者4的数的个数。

思路

暴力打表。注意包含4可以%10判断,是否包含38可以%100来判断。

H 了断局
题目描述

变形Fibonaci数列,从第4个数开始,每一个数都是前3个数的和。离线处理即可。

总结

原来Dp还有如此多的变种,虽然都只是变化一点点,但就是这一点点就把我难住了。写此题解mark一下吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值