题目
题意:
给定n个人,要求选出p个玩家,k个观众。挑选出第i个人为观众对答案贡献a[i]的值,挑选第i个人做为第j个玩家对答案贡献
s
i
j
s_{ij}
sij。
2
≤
n
≤
1
0
5
,
1
≤
p
≤
7
,
1
≤
k
,
p
+
k
≤
n
,
1
≤
a
i
≤
1
0
9
,
1
≤
s
i
j
≤
1
0
9
2≤n≤10^5,1≤p≤7,1≤k,p+k≤n,1≤a_i≤10^9,1≤s_{ij}≤10^9
2≤n≤105,1≤p≤7,1≤k,p+k≤n,1≤ai≤109,1≤sij≤109
分析:
对于每个人都有三种选择,不选,当玩家或者当观众,对于这样的问题显然是dp了。题目的特别之处在于选择当玩家有编号,每个人选为玩家的编号的不同还影响结果,由于p很小,所以这一维我们采用状压选择观众的结果(第i位为1代表第i个玩家已经选好)解决。
对于选择k个观众,由于k很大,我们不可能再用一维去维护它。根据贪心的策略我们会发现选观众肯定选a[i]大的那些部分。所以我们按照a[i]从大到小排序,遍历时维护选择k个观众。但是前k个不一定都能被选(一些被选择为当玩家了)。所以对于状压的状态分析其二进制的1的个数,来判断i之前有多少个人被选去当了玩家。那么剩下的就是被选择去当了观众,当被选择当观众的人数仍然小于k时,说明第i个人还可以被选择去当观众,再处理一下即可。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
struct node{
int id,val;
bool operator<(const node&n)const
{
return val > n.val;
}
}a[100005];
ll dp[100005][1<<7],s[100005][10];
int cal(int a)
{
int res = 0;
while( a )
{
if( a & 1 ) res ++;
a >>= 1;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,p,k;
cin >> n >> p >> k;
for (int i = 1; i <= n; i++)
{
cin >> a[i].val;
a[i].id = i;
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < p; j++)
{
cin >> s[i][j];
}
}
sort(a+1,a+1+n);
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < (1<<p); j++)
{
int num = cal(j);
if( i < num ) continue;
dp[i][j] = dp[i-1][j];
if( i - num <= k && i != num )
{
dp[i][j] = dp[i-1][j] + a[i].val;
}
for (int l = 0; l < p; l++)
{
if( j & (1<<l) ) dp[i][j] = max(dp[i][j],dp[i-1][j-(1<<l)] + s[a[i].id][l]);
}
}
}
cout << dp[n][(1<<p)-1] << '\n';
return 0;
}