题目:传送门
题意:你有 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 ", 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 ", dp[n][(1 << p) - 1]); } int main() { // int _; scanf("%d", &_); // while(_--) solve(); solve(); return 0; }