题目链接:https://codeforces.com/contest/1316/problem/E
题目大意:
有n个人,要选一支排球队伍,一共有p个位置,每个位置都需要一个人,同时需要k个观众,一个队员只能去一个位置,而且每个人要么去排球队要么去观众要么什么也不干,只能有一个身份,给出每个人当观众的值以及在每个位置上的值,求和最大值。 n ≤ 1 e 5 , p ≤ 7 , p + k ≤ 1 e 5 n≤1e5,p≤7,p+k≤1e5 n≤1e5,p≤7,p+k≤1e5
题目思路:
看到p的范围这么小,第一个想法肯定就是状压dp了,通常有这种异常小的数据范围都会这么想。。那么dp肯定有一维是枚举状态了,那么观众咋办呢?有个很巧妙的处理。可以发现一个事情,就是如果p个人已经选好了,那么观众选谁也就显而易见了,肯定是剩下的人里面最强的k个鸭!所以我们可以对观众根据观众值进行排序,然后dp转移的时候,直接根据,除了去打排球以外还有多少人,如果还缺人,直接上,因为除了队伍里的人,剩下的是越强的去越好,所以按大到小排序的话,那直接按照强的人先安排的原则贪心,就能搞定。所以dp转移很简单,枚举状态j,然后用i-j的位数,也就是除了去打排球的还剩多少人,如果剩余人数小于等于k,那么美滋滋,所有人都能去打排球,否则的话大佬观众已经够了就别进去掺和了。然后就是很套路的dp转移啦,直接枚举j这个状态枚举自己能顶替谁就行。
坑点一个是需要从0开始枚举起,因为有可能前面几个人大家都去当观众了,另一个坑点是初始化的时候除了 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]设为0,其他都要设为-inf,不然的话会出现非法状态的转移,过不了样例3。
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define per(i,a,b) for(ll i=a;i>=b;i--)
const ll MAXN =1e5+5;
const ll MAXM = 4e7+5;
const ll MOD = 998244353;
int n,p,k;
struct node{
int pos;
ll val;
}a[MAXN];
ll s[MAXN][10];
ll dp[MAXN][300];
bool cmp(node a,node b){
return a.val>b.val;
}
int main()
{
while(~scanf("%d%d%d",&n,&p,&k)){
memset(dp,-0x3f,sizeof(dp));
dp[0][0]=0;
rep(i,1,n)a[i].pos=i,scanf("%I64d",&a[i].val);
sort(a+1,a+n+1,cmp);
rep(i,1,n){
rep(j,1,p)scanf("%I64d",&s[i][j]);
}
int endd=(1<<p)-1;
rep(i,1,n){
rep(j,0,endd){
int num=i-__builtin_popcount(j);
if(num<0)continue;
dp[i][j]=dp[i-1][j];
if(num<=k){
dp[i][j]+=a[i].val;
}
rep(k,0,p-1){
if((1<<k)&j){
dp[i][j]=max(dp[i][j],dp[i-1][j-(1<<k)]+s[a[i].pos][k+1]);
}
}
}
}
printf("%I64d\n",dp[n][endd]);
}
}