2010-1137

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

vout自己填坑
题意:

{{{
Youmu要在神秘森林n个part中分别收集TIME和POINT以获取足够的力量抗击敌人。
有两个值PV和TV,在第i个part中,如果在某个时刻收集了一个POINT,那么总能量就会增加PV,同时TV值会增加a[i],
如果收集一个TIME,那么总能量就会增加TV,同时PV值会增加b[i]。
part[i]中有x[i]个POINT和y[i]个TIME。PV和TV值都会一直保留,带入下一个part。
现在的问题是,如何安排part的顺序,以及每个part里面收集POINT和TIME的顺序,使得最后能得到的总能量最大。
}}}

思路:

{{{
首先,对于每个part,设如果在PV和TV在这个part中初始都为0时,在这个part中能收集到的最大的能量为gao[i]。
再设进入这个part之前的PV值和TV值分别为PV0和TV0,那么在这个part中实际能得到的最大能量为PV0 * x[i] + TV0 * y[i] + gao[i]。
而每个part进行完后,PV和TV肯定是分别增加了b[i] * y[i]和a[i] * x[i],也就是说在只要知道在进入这个part之前,
已经完成了哪些part,连顺序都不用考虑,就可以知道PV0和TV0的值。因为n<=15,可以使用状态压缩的DP来求解。
再考虑如何算出gao[i]。
可以用dp的方法,设dp[x][y]为收集了x个POINT和y个TIME能得到的最大能量。
那么就有dp[x][y] = max(dp[x - 1][y] + b[i] * y, dp[x][y - 1] + a[i] * x). 这个时间复杂度为x[i] * y[i]。
}}}

这是asmn的代码:

{{{
#!cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <algorithm>

#define INF 0x3f3f3f3f
#define MP make_pair
const int MAXN = 1011;

using namespace std;
typedef long long LL;
LL dp[35][35], add[20];
LL A, B, X[35], Y[35];
LL PV[1 << 15], TV[1 << 15], ans[1 << 15];
LL gao(int i){

    for (int x = 0; x <= X[i]; ++x){
        for (int y = 0; y <= Y[i]; ++y){
            dp[x][y] = 0;
            if (x > 0){
                dp[x][y] = max(dp[x][y], dp[x - 1][y] + B * y);
            } 
            if (y > 0){
                dp[x][y] = max(dp[x][y], dp[x][y - 1] + A * x);
            }
        }
    }
    return dp[X[i]][Y[i]];
}
int main(){
    int N, i, mask;
    while (scanf("%d", &N) != EOF){
        memset(PV, 0, sizeof(PV));
        memset(TV, 0, sizeof(TV));
        memset(ans, 0, sizeof(ans));
        for (i = 0; i < N; ++i){
            scanf("%lld%lld%lld%lld", &A, &B, &X[i], &Y[i]);
            add[i] = gao(i);
            //printf("%lld\n", add[i]);
            PV[1 << i] = Y[i] * B;
            TV[1 << i] = X[i] * A;
        }

        for (mask = 1; mask < (1 << N); ++mask){
            PV[mask] = PV[mask & -mask] + PV[mask & (mask - 1)];
            TV[mask] = TV[mask & -mask] + TV[mask & (mask - 1)];
            //printf("%d: %lld %lld\n", mask, PV[mask], TV[mask]);
            for (i = 0; i < N; ++i){
                if (mask & (1 << i)){
                    ans[mask] = max(ans[mask], add[i] + X[i] * PV[mask ^(1 << i)] + Y[i] * TV[mask ^(1 << i)] + ans[mask ^ (1 << i)]);
                }
            }
        }
        printf("%lld\n", ans[(1 << N) - 1]);

    }

}
}}}
[[BR]]
此题还有贪心方法,不妨设某个part中a[i] > b[i]。那么收集方法应该是先把所有的POINT收集,再把所有的TIME收集。反之亦然。证明如下:

{{{
设一个收集序列为*****TP*****,T代表收集了TIME,P代表收集了POINT,
那么通过交换相邻的T和P的,一定可以使得到能量增加。
设收集这个T之前,PV和TV分别为PV1和TV1,那么按照TP的顺序,得到的能量为TV1 + (PV1 + b[i]),
收集完这两个后,PV = PV1 + b[i], TV = TV1 + a[i]。
如果按照PT的顺序,收集完这两个后,PV = PV1 + b[i], TV = TV1 + a[i]是一样的,
但是得到的能量就是PV1 + (TV1 + a[i]),比TP更多。
因此,凡是遇到TP,就变为PT,这样一来,最优的顺序肯定是PPPPP……TTTTT。
于是有:gao[i] = max(a[i], b[i]) * x[i] * y[i];
}}}

以下是标程的代码,用的就是贪心方法:

{{{
#!cpp
#include <cstdio>
#include <algorithm>
#include <cassert>
using namespace std;
typedef long long LL;
const int MAXN = 20 + 5;
int n;
int a[MAXN], b[MAXN], x[MAXN], y[MAXN], p[MAXN];
int dp[1 << 16];

int main() {
    while (scanf("%d", &n) != EOF) {
        assert(n >= 1 && n <= 15);
        for (int i = 0; i < n; i++) {
            scanf("%d%d%d%d", &a[i], &b[i], &x[i], &y[i]);
            p[i] = max(a[i], b[i]) * x[i] * y[i];
        }
        fill(dp, dp + (1 << n), 0);
        for (int i = 0; i < (1 << n); i++) {
            int pv = 0, tv = 0;
            for (int k = 0; k < n; k++) {
                if (i & (1 << k)) {
                    tv += a[k] * x[k];
                    pv += b[k] * y[k];
                }
            }
            for (int j = 0; j < n; j++) {
                if ((i & (1 << j)) == 0) {
                    dp[i | (1 << j)] = max(dp[i | (1 << j)], dp[i] + pv * x[j] + tv * y[j] + p[j]);
                }
            }
        }
        printf("%d\n", dp[(1 << n) - 1]);
    }
    return 0;
}
}}}

vout自己填坑

题意:

Youmu要在神秘森林n个part中分别收集TIME和POINT以获取足够的力量抗击敌人。
有两个值PV和TV,在第i个part中,如果在某个时刻收集了一个POINT,那么总能量就会增加PV,同时TV值会增加a[i],
如果收集一个TIME,那么总能量就会增加TV,同时PV值会增加b[i]。
part[i]中有x[i]个POINT和y[i]个TIME。PV和TV值都会一直保留,带入下一个part。
现在的问题是,如何安排part的顺序,以及每个part里面收集POINT和TIME的顺序,使得最后能得到的总能量最大。

思路:

首先,对于每个part,设如果在PV和TV在这个part中初始都为0时,在这个part中能收集到的最大的能量为gao[i]。
再设进入这个part之前的PV值和TV值分别为PV0和TV0,那么在这个part中实际能得到的最大能量为PV0 * x[i] + TV0 * y[i] + gao[i]。
而每个part进行完后,PV和TV肯定是分别增加了b[i] * y[i]和a[i] * x[i],也就是说在只要知道在进入这个part之前,
已经完成了哪些part,连顺序都不用考虑,就可以知道PV0和TV0的值。因为n<=15,可以使用状态压缩的DP来求解。
再考虑如何算出gao[i]。
可以用dp的方法,设dp[x][y]为收集了x个POINT和y个TIME能得到的最大能量。
那么就有dp[x][y] = max(dp[x - 1][y] + b[i] * y, dp[x][y - 1] + a[i] * x). 这个时间复杂度为x[i] * y[i]。

这是asmn的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <algorithm>
#define INF 0x3f3f3f3f
#define MP make_pair
const int MAXN = 1011;
using namespace std;
typedef long long LL;
LL dp[35][35], add[20];
LL A, B, X[35], Y[35];
LL PV[1 << 15], TV[1 << 15], ans[1 << 15];
LL gao(int i){
    for (int x = 0; x <= X[i]; ++x){
        for (int y = 0; y <= Y[i]; ++y){
            dp[x][y] = 0;
            if (x > 0){
                dp[x][y] = max(dp[x][y], dp[x - 1][y] + B * y);
            } 
            if (y > 0){
                dp[x][y] = max(dp[x][y], dp[x][y - 1] + A * x);
            }
        }
    }
    return dp[X[i]][Y[i]];
}
int main(){
    int N, i, mask;
    while (scanf("%d", &N) != EOF){
        memset(PV, 0, sizeof(PV));
        memset(TV, 0, sizeof(TV));
        memset(ans, 0, sizeof(ans));
        for (i = 0; i < N; ++i){
            scanf("%lld%lld%lld%lld", &A, &B, &X[i], &Y[i]);
            add[i] = gao(i);
            //printf("%lld\n", add[i]);
            PV[1 << i] = Y[i] * B;
            TV[1 << i] = X[i] * A;
        }
        for (mask = 1; mask < (1 << N); ++mask){
            PV[mask] = PV[mask & -mask] + PV[mask & (mask - 1)];
            TV[mask] = TV[mask & -mask] + TV[mask & (mask - 1)];
            //printf("%d: %lld %lld\n", mask, PV[mask], TV[mask]);
            for (i = 0; i < N; ++i){
                if (mask & (1 << i)){
                    ans[mask] = max(ans[mask], add[i] + X[i] * PV[mask ^(1 << i)] + Y[i] * TV[mask ^(1 << i)] + ans[mask ^ (1 << i)]);
                }
            }
        }
        printf("%lld\n", ans[(1 << N) - 1]);
    }
}


此题还有贪心方法,不妨设某个part中a[i] > b[i]。那么收集方法应该是先把所有的POINT收集,再把所有的TIME收集。反之亦然。证明如下:

设一个收集序列为*****TP*****,T代表收集了TIME,P代表收集了POINT,
那么通过交换相邻的T和P的,一定可以使得到能量增加。
设收集这个T之前,PV和TV分别为PV1和TV1,那么按照TP的顺序,得到的能量为TV1 + (PV1 + b[i]),
收集完这两个后,PV = PV1 + b[i], TV = TV1 + a[i]。
如果按照PT的顺序,收集完这两个后,PV = PV1 + b[i], TV = TV1 + a[i]是一样的,
但是得到的能量就是PV1 + (TV1 + a[i]),比TP更多。
因此,凡是遇到TP,就变为PT,这样一来,最优的顺序肯定是PPPPP……TTTTT。
于是有:gao[i] = max(a[i], b[i]) * x[i] * y[i];

以下是标程的代码,用的就是贪心方法:

#include <cstdio>
#include <algorithm>
#include <cassert>
using namespace std;
typedef long long LL;
const int MAXN = 20 + 5;
int n;
int a[MAXN], b[MAXN], x[MAXN], y[MAXN], p[MAXN];
int dp[1 << 16];
int main() {
    while (scanf("%d", &n) != EOF) {
        assert(n >= 1 && n <= 15);
        for (int i = 0; i < n; i++) {
            scanf("%d%d%d%d", &a[i], &b[i], &x[i], &y[i]);
            p[i] = max(a[i], b[i]) * x[i] * y[i];
        }
        fill(dp, dp + (1 << n), 0);
        for (int i = 0; i < (1 << n); i++) {
            int pv = 0, tv = 0;
            for (int k = 0; k < n; k++) {
                if (i & (1 << k)) {
                    tv += a[k] * x[k];
                    pv += b[k] * y[k];
                }
            }
            for (int j = 0; j < n; j++) {
                if ((i & (1 << j)) == 0) {
                    dp[i | (1 << j)] = max(dp[i | (1 << j)], dp[i] + pv * x[j] + tv * y[j] + p[j]);
                }
            }
        }
        printf("%d\n", dp[(1 << n) - 1]);
    }
    return 0;
}