链接:https://ac.nowcoder.com/acm/contest/26/E
来源:牛客网
题目描述
珂...珂...珂朵莉给你出了一道送分题:
给你一个长为n的序列{vi},和一个数a,你可以从里面选出最多m个数
一个合法的选择的分数定义为选中的这些数的和加上额外规则的加分:
有b个额外的规则,第i个规则即为:
对于这个序列的所有长为a的连续子区间,如果这个子区间中对应的给出的xi个位置都被选中了,则这次选择的分数加上yi(yi可能为负数,这种情况下分数仍然要加上y)
输入描述:
第一行四个数n,m,a,b
之后一行n个数表示序列v
之后表示b个规则
每个规则先输入两个数xi和yi
之后一行xi个数,分别表示这给定的xi个位置
输出描述:
一行一个数表示最大可能得到的分数
备注:
对于100%的数据,0 <= n <= 100 , 0 <= m <= 50 ,0
<= a <= 16 , 0 <= b <= 100000, 所有出现的数的绝对值<=
600
解题思路:
首先预处理出每个状态下能得到的额外的分数,定义状态dp【i】【j】【k】为前i个数中取j个数,最后a个数的状态为k时的最大分数,那么状态转移方程为:取当第i+1个数字时:
dp[1^flag][j+1][(k>>1)|(1<<(a-1))]=max(dp[1^flag][j+1][(k>>1)|(1<<(a-1))],dp[flag][j][k]+eval[(k>>1)|(1<<(a-1))]+val[i]);
不取第i+1个数字:dp[1^flag][j][k>>1]=max(dp[1^flag][j][k>>1],dp[flag][j][k]+eval[k>>1]);
这里我用了滚动数组。
#include<bits/stdc++.h> #define inf 0x3f3f3f3f using namespace std; int eval[1<<16]; int val[105]; int dp[2][55][1<<16]; int main(){ int n,m,a,b; scanf("%d%d%d%d",&n,&m,&a,&b); for(int i=0;i<n;i++){ scanf("%d",&val[i]); } for(int i=0;i<b;i++){ int x,y; int tem=0; int tet; scanf("%d%d",&x,&y); while(x--){ scanf("%d",&tet); tet--; tem=tem|(1<<tet); } eval[tem]+=y; // cout<<tem<<endl; } for(int i=(1<<a)-1;i>=0;i--) { if(eval[i]!=0) { int j=(1<<a)-1-i; for(int s=j;s>0;s=(s-1)&j) eval[i|s]+=eval[i]; } } for(int i=0;i<=m;i++){ for(int j=0;j<(1<<a);j++){ dp[0][i][j]=-inf; } } for(int j=0;j<(1<<a);j++){ int tem=0; int cnt=0; for(int k=0;k<a;k++){ if(j&(1<<(k))){ tem+=val[k]; cnt++; } } dp[0][cnt][j]=max(dp[0][cnt][j],tem+eval[j]); } int flag=0; for(int i=a;i<n;i++){ for(int j=0;j<=m;j++){ for(int k=0;k<(1<<a);k++){ dp[1^flag][j][k]=-inf; } } for(int j=0;j<=m;j++){ for(int k=0;k<(1<<a);k++){ // int sta=k; if(dp[flag][j][k]==-inf)continue; int tem=(k>>1)|(1<<(a-1)); dp[1^flag][j][k>>1]=max(dp[1^flag][j][k>>1],dp[flag][j][k]+eval[k>>1]); dp[1^flag][j+1][tem]=max(dp[1^flag][j+1][tem],dp[flag][j][k]+eval[tem]+val[i]); } } flag=flag^1; } int ans=-inf; for(int i=0;i<=m;i++){ for(int j=0;j<(1<<a);j++){ ans=max(ans,dp[flag][i][j]); } } printf("%d ",ans); return 0; }