E. Team Building (状压dp + 思维)

题目:传送门

题意:你有 n 个人,你想从这 n 个人中选 p 个人去到不同的 p 个位置, 选 k 个人作为观众。如果第 i 个人被选为观众他的贡献就是 a[ i ],如果第 i 个人被选为第 j 个位置上的人,那么他的贡献就是 b[ i ][ j ]。问你选 p 个位置上的人和 k 个观众最大的贡献是多少。

   1 <= n <= 1e5, 1 <= p <= 7, p + k <= n

 

思路:

这个题,显然是一个状压 dp, dp[ i ][ j ] = 前 i 个人,已选的位置的状态是 j 的最大贡献。 如果不需要选观众的话,这样就已经足够了。

现在考虑观众要怎么选,我们可以对每个人作为观众时的贡献按降序排个序,即对 a 数组排序,然后,我们肯定优先选择贡献最大的前 k 个人。

然后我们维护一个 pos[ i ] 表示 排序前的 a[ i ]排序后所在的位置的下标。

这样的话,我们在递推的过程中,就要考虑当前这个人是不是已经被选为观众了。怎么考虑呢?

如果,我们当前枚举到的第 i 个人,它的 pos[ i ] <= k,那么它肯定是已经被选为观众的,那我们现在想让它做为某个位置上的人,那就得取消掉它作为观众的贡献,然后再重新选一名观众咯。那我们就减掉 a[ pos[ i ] ] 然后再加上 a[ k + 1 ]。这样的话,如果下一次,我们选了 pos[ i ] = k + 1 的人作为某个位置上的人的话呢,我们就不能只是单纯的通过判断 pos[ i ] <= k 来判断他是不是作为观众。那咋整啊。

我们可以再维护一个 c[ i ][ j ] 表示前 i 个人已选的位置的状态是 j 的最优情况,有 c[ i ][ j ] 曾经作为观众。 这样的话,我们可以通过 pos[ i ] <= k + c[ i ][ j ] 来判断他是否是观众。

 

这里还有一个注意的点,就是,我们枚举 i 的时候,得按照他们作为观众时的贡献 从大到小枚举。

如果你没有这样枚举的话,会有这么一种情况,就是,你先枚举到了 pos[ i ] = k + 1 的数,然后你判断的时候,它没有作为观众。后来,你又枚举到了 pos[ i ] = k,你发现它是观众,所以你把它作为观众的贡献减去了,然后加上了 pos[ i ] = k + 1 作为观众的贡献,但是,此时你 pos[ i ] = k + 1 已经早就被你选为某个位置上的人了。 这样就会出错,我一开始没注意到,一直wa37,后面会给出错误示范的代码。

 

在这里, c[ i ][ j ] 这个数组是可以不要的,你按照 a[ i ] 从大到小枚举的时候,你的 j 的二进制位上有多少个 1 就代表着你已经选好了多少个位置上的人,我们设有 cnt 个 1 好了,那你的前 cnt + k 个人要么是观众,要么是某个位置上的人, 所以你判断他是否被选为观众,你只需要判断,它 pos[ i ] <= cnt + k 是否成立就好了。

 

我的代码可能跟我说的不太一样,但是思路是差不多的,我这份代码没有优化 c[ i ][ j ]。

 

#include <bits/stdc++.h>
#define LL long long
#define mem(i, j) memset(i, j, sizeof(i))
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define INF INT_MAX
#define inf LLONG_MAX
#define PI acos(-1)
#define fir first
#define sec second
using namespace std;
 
const int N = 1e5 + 5;
 
LL b[N][10];
 
LL a[N];
int pos[N]; /// a[i]排名后下标为 i 的在排名前的下标是 pos[i]
 
LL dp[N][130]; 
int c[N][130];
 
bool cmp(int i, int j) {
    return a[i] > a[j];
}
 
void solve() {
 
    int n, p, K;
    scanf("%d %d %d", &n, &p, &K);
 
    rep(i, 1, n) scanf("%lld", &a[i]), pos[i] = i;
    sort(pos + 1, pos + 1 + n, cmp); /// pos[ i ] 存的是 a 数组中第 i 大的数的下标
 
    rep(i, 1, n) rep(j, 0, p - 1) scanf("%lld", &b[i][j]);
    LL res = 0;
    rep(i, 1, K) res += a[pos[i]]; /// 先把前 k 大的累加起来,这些先作为观众
 
    rep(i, 0, (1 << p) - 1) dp[0][i] = res, c[0][i] = 0;
 
 
    rep(i, 1, n) { /// 按照数组 a 降序枚举
        rep(j, 0, (1 << p) - 1) dp[i][j] = dp[i - 1][j], c[i][j] = c[i - 1][j]; /// 初始化
        rep(j, 0, (1 << p) - 1) {
            rep(k, 0, p - 1) {
                if((j >> k) & 1) continue;
 
                if(i <= (c[i - 1][j] + K)) { ///被选为观众
                    int id = c[i - 1][j] + 1 + K;
                    LL add = a[pos[id]] - a[pos[i]];
                    if(dp[i - 1][j] + b[pos[i]][k] + add > dp[i][j | (1 << k)]) {
                        dp[i][j | (1 << k)] = dp[i - 1][j] + b[pos[i]][k] + add;
                        c[i][j | (1 << k)] = c[i - 1][j] + 1;
                    }
                }
                else { /// 不作为观众
                    if(dp[i - 1][j] + b[pos[i]][k] > dp[i][j | (1 << k)]) {
                        dp[i][j | (1 << k)] = dp[i - 1][j] + b[pos[i]][k];
                        c[i][j | (1 << k)] = c[i - 1][j];
                    }
                }
            }
        }
    }
 
    printf("%lld\n", dp[n][(1 << p) - 1]);
 
}
 
int main() {
 
//    int _; scanf("%d", &_);
//    while(_--) solve();
 
    solve();
 
    return 0;
}

 

 

错误示范

 

#include <bits/stdc++.h>
#define LL long long
#define mem(i, j) memset(i, j, sizeof(i))
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define INF INT_MAX
#define inf LLONG_MAX
#define PI acos(-1)
#define fir first
#define sec second
using namespace std;
 
const int N = 1e5 + 5;
 
LL b[N][10];
bool vis[N];
pair < LL, int > a[N];
 
LL dp[N][130];
int c[N][130];
int pos[N];
bool cmp(pair < LL, int > A, pair < LL, int > B) {
    return A.fir > B.fir;
}
 
void solve() {
 
    int n, p, K;
    scanf("%d %d %d", &n, &p, &K);
 
    rep(i, 1, n) scanf("%lld", &a[i].fir), a[i].sec = i;
    sort(a + 1, a + 1 + n, cmp);
    rep(i, 1, n) pos[a[i].sec] = i;
    rep(i, 1, n) rep(j, 0, p - 1) scanf("%lld", &b[i][j]);
    LL res = 0;
    rep(i, 1, K) res += a[i].fir, vis[a[i].sec] = 1;
 
    rep(i, 0, (1 << p) - 1) dp[0][i] = res, c[0][i] = 0;
 
    rep(i, 1, n) {
        rep(j, 0, (1 << p) - 1) dp[i][j] = dp[i - 1][j], c[i][j] = c[i - 1][j];
        rep(j, 0, (1 << p) - 1) {
            rep(k, 0, p - 1) {
                if((j >> k) & 1) continue;
 
                if(pos[i] <= c[i - 1][j] + K) {
                    int id = c[i - 1][j] + 1 + K;
//                    cout << c[i - 1][j] << " " << K << " " << id << endl;
                    LL add = a[id].fir - a[pos[i]].fir;
//                    cout << id << " " << pos[i] << " " << add << endl;
                    if(dp[i - 1][j] + b[i][k] + add > dp[i][j | (1 << k)]) {
                        dp[i][j | (1 << k)] = dp[i - 1][j] + b[i][k] + add;
                        c[i][j | (1 << k)] = c[i - 1][j] + 1;
                    }
                }
                else {
                    if(dp[i - 1][j] + b[i][k] > dp[i][j | (1 << k)]) {
                        dp[i][j | (1 << k)] = dp[i - 1][j] + b[i][k];
                        c[i][j | (1 << k)] = c[i - 1][j];
                    }
                }
            }
        }
    }
 
    printf("%lld\n", dp[n][(1 << p) - 1]);
 
}
 
int main() {
 
//    int _; scanf("%d", &_);
//    while(_--) solve();
 
    solve();
 
    return 0;
}

 

posted on 2020-03-05 15:45  Willems  阅读(379)  评论(0编辑  收藏  举报

导航