• 2021NUAA暑假集训 Day5 部分题解


    比赛链接:21.7.16-NUAA暑期集训
    比赛码:NUAAACM20210716



    A - Mr. Young's Picture Permutations

    注意到(k)很小,我们就可以暴力状态了,而要表现单调递增,故维护一个从左至右边矮的阶梯。
    以有(5)列为例,设(f[a][b][c][d][e])表示第(1)列已经填的数字高度为(a),第(2)列高度为(b),...,第(5)列的高度为(e)的,现在填到数字(a+b+c+d+e)方案数,并保证转移时(a geq b geq c geq d geq e),于是当一个新的数字填的时候一定比所有的数字都要大。
    递推式为:

    [f[a+1][b][c][d][e]+=f[a][b][c][d][e] ]

    [f[a][b+1][c][d][e]+=f[a][b][c][d][e] ]

    [f[a][b][c+1][d][e]+=f[a][b][c][d][e] ]

    [f[a][b][c][d+1][e]+=f[a][b][c][d][e] ]

    [f[a][b][c][d][e+1]+=f[a][b][c][d][e] ]

    边界:(f[0][0][0][0][0]=1),其余为(0)
    答案:(f[n1][n2][n3][n4][n5])

    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    int row, n[6];
    
    int main() {
        while (scanf("%d", &row) && row) {
            memset(n, 0, sizeof(n));
            for (int i = 1; i <= row; ++i) {
                scanf("%d", &n[i]);
            }
            unsigned int f[n[1] + 1][n[2] + 1][n[3] + 1][n[4] + 1][n[5] + 1];
            memset(f, 0, sizeof(f));
            f[0][0][0][0][0] = 1;
            for (int i1 = 0; i1 <= n[1]; ++i1) {
                for (int i2 = 0; i2 <= n[2]; ++i2) {
                    for (int i3 = 0; i3 <= n[3]; ++i3) {
                        for (int i4 = 0; i4 <= n[4]; ++i4) {
                            for (int i5 = 0; i5 <= n[5]; ++i5) {
                                unsigned int t = f[i1][i2][i3][i4][i5];
                                if (i1 < n[1]) {
                                    f[i1 + 1][i2][i3][i4][i5] += t;
                                }
                                if (i2 < n[2] && i1 > i2) {
                                    f[i1][i2 + 1][i3][i4][i5] += t;
                                }
                                if (i3 < n[3] && i2 > i3) {
                                    f[i1][i2][i3 + 1][i4][i5] += t;
                                }
                                if (i4 < n[4] && i3 > i4) {
                                    f[i1][i2][i3][i4 + 1][i5] += t;
                                }
                                if (i5 < n[5] && i4 > i5) {
                                    f[i1][i2][i3][i4][i5 + 1] += t;
                                }
                            }
                        }
                    }
                }
            }
            printf("%u
    ", f[n[1]][n[2]][n[3]][n[4]][n[5]]);
        }
        return 0;
    }
    

    B - 石子合并

    区间dp模板题,由于是圆形操场,所以将数据再拓展一遍,用前缀和存储石子堆数量。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    #define INF 0x3f3f3f3f
    
    int n, ansmin, ansmax, dpmax[300][300], dpmin[300][300], num[300], sum[300];
    
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &num[i]);
            num[i + n] = num[i];
        }
        for (int i = 1; i <= 2 * n; ++i) {
            sum[i] = sum[i - 1] + num[i];
        }
        for (int len = 1; len < n; ++len) {
            for (int i = 1; i + len <= 2 * n; ++i) {
                int j = i + len;
                dpmin[i][j] = INF, dpmax[i][j] = 0;
                for (int k = i; k < j; ++k) {
                    dpmax[i][j] = max(dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j] + sum[j] - sum[i - 1]);
                    dpmin[i][j] = min(dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j] + sum[j] - sum[i - 1]);
                }
            }
        }
        ansmin = INF, ansmax = 0;
        for (int i = 1; i <= n; i++) {
            ansmax = max(ansmax, dpmax[i][i + n - 1]);
            ansmin = min(ansmin, dpmin[i][i + n - 1]);
        }
        cout << ansmin << endl << ansmax << endl;
        return 0;
    }
    

    C - 小奇采药

    乍一看以为是背包模板题,但看到数据(m)的范围就知道背包会超时,于是考虑搜索并剪枝。
    先按时间从大到小排序,求一个后缀和,(lastw[i])表示(i)(n)的时间和,(lastv[i])表示(i)(n)的价值和,然后跑(dfs)

    以下三种情况可以剪枝:

    1. 如果当前的时间和加上(a[n].w)还超过了背包容量,也就是不管用哪一件往背包里塞都装不下了,就退出;
    2. 如果当前的价值和加上(lastv[k])也就是剩下的价值和仍然小于最优值,就退出;
    3. 如果当前的时间和加上(lastw[k])也就是剩下的时间和仍然小于背包容量,就拿当前的价值和加上(lastv[k])和原最优解比较,如果当前的更优,更新答案,这时候就没有再递归下去的必要了,直接退出。
    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    using namespace std;
    #define ll long long
    
    ll ans = 0, m, lastw[155], lastv[155];
    int n, t;
    
    struct node {
        int w, v;
        bool operator<(const node &obj) const { return w > obj.w; }
    } a[155];
    
    void dfs(int k, ll sumw, ll sumv) {
        ans = max(ans, sumv);
        if (sumw + a[n].w > m || sumv + lastv[k] <= ans) {
            return;
        }
        if (sumw + lastw[k] <= m) {
            ans = max(ans, sumv + lastv[k]);
            return;
        }
        if (sumw + a[k].w <= m) {
            dfs(k + 1, sumw + a[k].w, sumv + a[k].v);
        }
        dfs(k + 1, sumw, sumv);
    }
    
    int main() {
        scanf("%d", &t);
        while (t--) {
            scanf("%d%lld", &n, &m);
            for (int i = 1; i <= n; ++i) {
                scanf("%d%d", &a[i].w, &a[i].v);
            }
            sort(a + 1, a + n + 1);
            lastw[n + 1] = 0;
            lastv[n + 1] = 0;
            for (int i = n; i >= 1; i--) {
                lastw[i] = lastw[i + 1] + a[i].w;
                lastv[i] = lastv[i + 1] + a[i].v;
            }
            ans = 0;
            dfs(1, 0, 0);
            printf("%lld
    ", ans);
        }
        return 0;
    }
    

    D - 拦截导弹

    最多能拦截多少导弹就是求最长不上升子序列,最少要配备多少套系统就是求最长上升序列。

    #include <algorithm>
    #include <iostream>
    using namespace std;
    #define N 100010
    
    int a[N], f1[N], f2[N], n;
    
    int main() {
        while (cin >> a[++n]);
        n--;
        int len1 = 1, len2 = 1;
        f1[1] = a[1];
        f2[1] = a[1];
        for (int i = 2; i <= n; ++i) {
            if (f1[len1] >= a[i]) {
                f1[++len1] = a[i];
            } else {
                int p1 = upper_bound(f1 + 1, f1 + 1 + len1, a[i], greater<int>()) - f1;
                f1[p1] = a[i];
            }
            if (f2[len2] < a[i]) {
                f2[++len2] = a[i];
            } else {
                int p2 = lower_bound(f2 + 1, f2 + 1 + len2, a[i]) - f2;
                f2[p2] = a[i];
            }
        }
        cout << len1 << endl << len2;
        return 0;
    }
    

    E - 合唱队形

    正反各求一次最长上升序列,对每个点取正反两次以该点为最高点的最长上升子序列长度之和(注意该点被取两次,需要减一)即为以该点为最高点的最长合唱队列。

    #include <algorithm>
    #include <iostream>
    using namespace std;
    #define N 110
    
    int n, dp1[N], dp2[N], h[N], t1[N], t2[N], len1, len2, ans;
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            cin >> h[i];
        }
        dp1[++len1] = h[1];
        t1[1] = 1;
        for (int i = 1; i <= n; ++i) {
            if (dp1[len1] < h[i]) {
                dp1[++len1] = h[i];
                t1[i] = len1;
            } else {
                int p1 = lower_bound(dp1 + 1, dp1 + len1 + 1, h[i]) - dp1;
                dp1[p1] = h[i];
                t1[i] = p1;
            }
        }
        dp2[++len2] = h[n];
        t2[n] = 1;
        for (int i = n; i > 0; --i) {
            if (dp2[len2] < h[i]) {
                dp2[++len2] = h[i];
                t2[i] = len2;
            } else {
                int p2 = lower_bound(dp2 + 1, dp2 + len2 + 1, h[i]) - dp2;
                dp2[p2] = h[i];
                t2[i] = p2;
            }
        }
        for (int i = 1; i <= n; ++i) {
            ans = max(ans, t1[i] + t2[i] - 1);
        }
        cout << n - ans;
        return 0;
    }
    

    F - Cash Machine

    多重背包模板题,需要用到二进制拆分,将物品数(n_i)拆成(1,2,4,8,...,2^{k},x(x leq 2^{k + 1})),则这些数的组合可以表示([1,n_i])的所有数,就不需要循环从(1)跑到(n_i)了。

    #include <cstring>
    #include <iostream>
    using namespace std;
    #define N 110
    
    int dp[100010], cash, n, cnt, ni, di;
    struct Node {
        int w, v;
    } node[110];
    
    void addNode(int n, int d) {
        int p = 1;
        while (n) {
            if (n >= p) {
                node[++cnt].w = d * p;
                node[cnt].v = d * p;
                n -= p;
                p <<= 1;
            } else {
                node[++cnt].w = d * n;
                node[cnt].v = d * n;
                n = 0;
            }
        }
    }
    
    int main() {
        while (cin >> cash >> n) {
            memset(dp, 0, sizeof(dp));
            cnt = 0;
            for (int i = 1; i <= n; ++i) {
                cin >> ni >> di;
                addNode(ni, di);
            }
            for (int i = 1; i <= cnt; ++i) {
                for (int j = cash; j >= node[i].w; --j) {
                    dp[j] = max(dp[j], dp[j - node[i].w] + node[i].v);
                }
            }
            cout << dp[cash] << endl;
        }
        return 0;
    }
    

    G - Coins

    (dp[i][j])为前(i)种面值的硬币组成面额(j)时,第(i)种硬币剩余的数量。
    默认(dp[i][j] = -1),表示无法组成面额(j)
    (dp[i - 1][j]geq 0),则说明已经可以组成该面额,不需要第(i)种硬币,所以(dp[i][j] = c[i])
    否则就看(dp[i][j-a[i]])的值,若(j < a[i]),显然无法通过第(i)种硬币组成面额(j),则(dp[i][j]=-1);若(dp[i][j-a[i]]leq 0),则表示第(i)种硬币已用完,则(dp[i][j]=-1);否则(dp[i][j]=dp[i][j-a[i]]-1)
    可以将二维数组压缩为一维。

    #include <cstring>
    #include <iostream>
    using namespace std;
    
    int n, m, dp[100010], a[110], c[110], ans;
    
    int main() {
        while (cin >> n >> m && (n || m)) {
            for (int i = 1; i <= n; ++i) {
                cin >> a[i];
            }
            for (int i = 1; i <= n; ++i) {
                cin >> c[i];
            }
            memset(dp, -1, sizeof(dp));
            dp[0] = 0;
            for (int i = 1; i <= n; ++i) {
                for (int j = 0; j <= m; ++j) {
                    if (dp[j] >= 0) {
                        dp[j] = c[i];
                    } else if (j < a[i] || dp[j - a[i]] <= 0) {
                        dp[j] = -1;
                    } else {
                        dp[j] = dp[j - a[i]] - 1;
                    }
                }
            }
            ans = 0;
            for (int i = 1; i <= m; ++i) {
                if (dp[i] >= 0) {
                    ans++;
                }
            }
            cout << ans << endl;
        }
        return 0;
    }
    

    H - 能量项链

    一道典型的区间(dp)题。
    状态转移方程:

    [dp[i][j] = max{dp[i][j], dp[i][k] + dp[k + 1][j] + a[i] * a[k + 1] * a[j + 1]},ileq k < j ]

    #include <iostream>
    using namespace std;
    #define N 1010
    
    int a[N], n;
    long long dp[N][N], ans;
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
            a[n + i] = a[i];
        }
        for (int len = 1; len < n; ++len) {
            for (int i = 1; i + len < 2 * n; ++i) {
                int j = i + len;
                dp[i][j] = 0;
                for (int k = i; k < j; ++k) {
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + a[i] * a[k + 1] * a[j + 1]);
                }
            }
        }
        for (int i = 1; i <= n; ++i) {
            ans = max(ans, dp[i][i + n - 1]);
        }
        cout << ans;
        return 0;
    }
    

    I -


    
    

    J -


    
    

    K - Polygon

    区间(dp)题。

    1. 如果操作符是“(+)”:
    区间([L, R])的最大值(=)左区间([L, K])的最大值(+)右区间([K + 1, R])的最大值;
    区间([L, R])的最小值(=)左区间([L, K])的最小值(+)右区间([K + 1, R])的最小值。

    2. 如果操作符是“( imes)”:
    (minL)表示左区间([L, K])的最小值,
    (maxL)表示左区间([L, K])的最大值,
    (minR)表示右区间([K + 1, R])的最小值,
    (maxR)表示右区间([K + 1, R])的最大值,
    区间([L, R])的最大值(=max{minL * minR, minL * maxR, maxL * minR, maxL * maxR})
    区间([L, R])的最小值(=min{minL * minR, minL * maxR, maxL * minR, maxL * maxR})

    注意(K)的取值范围(Lleq K < R)

    #include <iostream>
    #include <cstring>
    #include <vector>
    using namespace std;
    #define INF 0x3f3f3f3f
    
    int dpmax[110][110], dpmin[110][110], n, ans;
    char oper[110];
    vector<int> vec;
    
    int max_4(int a, int b, int c, int d) {
        return max(a * c, max(a * d, max(b * c, b * d)));
    }
    
    int min_4(int a, int b, int c, int d) {
        return min(a * c, min(a * d, min(b * c, b * d)));
    }
    
    int main() {
        cin >> n;
        memset(dpmax, -0x3f, sizeof(dpmax));
        memset(dpmin, 0x3f, sizeof(dpmin));
        for (int i = 1; i <= n; ++i) {
            cin >> oper[i] >> dpmax[i][i];
            oper[i + n] = oper[i];
            dpmin[i][i] = dpmin[i + n][i + n] = dpmax[i + n][i + n] = dpmax[i][i];
        }
        for (int len = 2; len <= n; ++len) {
            for (int i = 1; i + len - 1 < 2 * n; ++i) {
                int j = i + len - 1;
                for (int k = i; k < j; ++k) {
                    if (oper[k + 1] == 't') {
                        dpmax[i][j] = max(dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j]);
                        dpmin[i][j] = min(dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j]);
                    } else {
                        dpmax[i][j] = max(dpmax[i][j], max_4(dpmax[i][k], dpmin[i][k], dpmax[k + 1][j], dpmin[k + 1][j]));
                        dpmin[i][j] = min(dpmin[i][j], min_4(dpmax[i][k], dpmin[i][k], dpmax[k + 1][j], dpmin[k + 1][j]));
                    }
                }
            }
        }
        ans = -INF;
        for (int i = 1; i <= n; ++i) {
            if (ans < dpmax[i][i + n - 1]) {
                ans = dpmax[i][i + n - 1];
                vec.clear();
                vec.push_back(i);
            } else if (ans == dpmax[i][i + n - 1]) {
                vec.push_back(i);
            }
        }
        cout << ans << endl;
        for (int i = 0; i < vec.size(); ++i) {
            cout << vec[i] << ' ';
        }
        return 0;
    }
    

    L - 飞扬的小鸟

    按照横坐标从左往右递推,设(dp[i][j])为到达点((i,j))所需的最小点击次数。
    状态转移方程:

    [dp[i][j] = min{dp[i][j], dp[i - 1][j - x[i]] + 1, dp[i][j-x[i]] + 1} ]

    [dp[i][j] = min{dp[i][j], dp[i - 1][j + y[i]]} ]

    因为触顶之后不会再上升,所以(dp[i][m] = min{dp[i][k],mleq k leq m+x[i]})
    如果所在的点有管道,则(dp[i][j]=0)

    #include <cstring>
    #include <iostream>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define N 10010
    #define M 2010
    
    int n, m, p, x[N], y[N], low[N], high[N], dp[N][M], ans;
    bool vis[N];
    
    int main() {
        ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
        cin >> n >> m >> p;
        for (int i = 1; i <= n; ++i) {
            cin >> x[i] >> y[i];
        }
        for (int i = 1; i <= n; ++i) {
            low[i] = 1, high[i] = m;
        }
        for (int i = 1, k, l, h; i <= p; ++i) {
            cin >> k >> l >> h;
            vis[k] = true;
            low[k] = l + 1;
            high[k] = h - 1;
        }
        memset(dp, 0x3f, sizeof(dp));
        for (int i = 1; i <= m; ++i) {
            dp[0][i] = 0;
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = x[i] + 1; j <= m + x[i]; ++j) {
                dp[i][j] = min(dp[i][j], min(dp[i - 1][j - x[i]] + 1, dp[i][j - x[i]] + 1));
            }
            for (int j = m + 1; j <= m + x[i]; ++j) {
                dp[i][m] = min(dp[i][m], dp[i][j]);
            }
            for (int j = 1; j <= m - y[i]; ++j) {
                dp[i][j] = min(dp[i][j], dp[i - 1][j + y[i]]);
            }
            for (int j = 1; j < low[i]; ++j) {
                dp[i][j] = dp[0][0];
            }
            for (int j = high[i] + 1; j <= m; ++j) {
                dp[i][j] = dp[0][0];
            }
        }
        ans = INF;
        for (int i = 1; i <= m; ++i) {
            ans = min(ans, dp[n][i]);
        }
        if (ans < INF) {
            cout << 1 << endl << ans << endl;
        } else {
            int i, j;
            for (i = n; i >= 1; --i) {
                for (j = 1; j <= m; ++j) {
                    if (dp[i][j] < INF) {
                        break;
                    }
                }
                if (j <= m) {
                    break;
                }
            }
            ans = 0;
            for (j = 1; j <= i; ++j) {
                if (vis[j]) {
                    ans++;
                }
            }
            cout << 0 << endl << ans << endl;
        }
        return 0;
    }
    

    M - 采药

    (01)背包模板题。

    #include <iostream>
    using namespace std;
    
    int dp[1010], t, m, w, v;
    
    int main() {
        cin >> t >> m;
        for (int i = 1; i <= m; ++i) {
            cin >> w >> v;
            for (int j = t; j >= w; --j) {
                dp[j] = max(dp[j], dp[j - w] + v);
            }
        }
        cout << dp[t];
        return 0;
    }
    
  • 相关阅读:
    Android_PopupWindow提示框
    视图字段对应属性列表
    odoo 关系字段(关联关系)
    odoo字段属性
    odoo xml中添加数据的数字代表含义
    odoo 常用模型的简写
    odoo标识符
    odoo 权限文件说明
    Odoo的菜单项
    Odoo的 数据添加修改删除代码和对应的方式
  • 原文地址:https://www.cnblogs.com/IzumiSagiri/p/15036855.html
Copyright © 2020-2023  润新知