牛客 26E 珂学送分2 (状压dp)

珂...珂...珂朵莉给你出了一道送分题: 

给你一个长为n的序列{vi},和一个数a,你可以从里面选出最多m个数 

一个合法的选择的分数定义为选中的这些数的和加上额外规则的加分: 

有b个额外的规则,第i个规则即为: 

对于这个序列的所有长为a的连续子区间,如果这个子区间中对应的给出的xi个位置都被选中了,则这次选择的分数加上yi(yi可能为负数,这种情况下分数仍然要加上y)

 

 

直接暴力枚举子集, 复杂度是$O(3^n+nm2^n)$. 

#include <iostream>
#include <memset.h>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;

const int N = 110, INF = 0xefefefef;
int n,m,a,b;
int v[N], f[1<<16], dp[2][55][1<<16], c[1<<16];
void chkmax(int &a, int b) {a<b?a=b:0;}

int main() {
	scanf("%d%d%d%d", &n, &m, &a, &b);
	a = min(a, n);
	REP(i,1,n) scanf("%d", v+i);
	REP(i,1,b) {
		int x, y, s = 0, t, mx = 0;
		scanf("%d%d",&x,&y);
		while (x--) {
			scanf("%d", &t);
			s ^= 1<<t-1;
		}
		f[s] += y; 
	}
	memset(dp,INF,sizeof dp);
	int mx = (1<<a)-1, cur = 0;
	REP(i,0,mx) {
		int &s = dp[cur][__builtin_popcount(i)][i] = 0;
		for (int j=i; j; --j&=i) s += f[j];
		REP(j,0,a-1) if (i>>j&1) s += v[j+1];
	}
	memset(c,0xef,sizeof c);
	REP(i,2,n-a+1) {
		cur ^= 1;
		memset(dp[cur],INF,sizeof dp[cur]);
		REP(j,0,m) REP(k,0,mx) if (dp[!cur][j][k]!=INF) {
			int &r = dp[!cur][j][k];
			int nxt = k>>1^1<<a-1;
			if (c[nxt]==INF) {
				c[nxt] = 0;
				for (int x=nxt; x; --x&=nxt) c[nxt] += f[x];
			}
			chkmax(dp[cur][j+1][nxt],r+v[i+a-1]+c[nxt]);
			nxt = k>>1;
			if (c[nxt]==INF) {
				c[nxt] = 0;
				for (int x=nxt; x; --x&=nxt) c[nxt] += f[x];
			}
			chkmax(dp[cur][j][nxt],r+c[nxt]);
		}
	}
	int ans = 0;
	REP(i,0,m) REP(j,0,mx) chkmax(ans,dp[cur][i][j]);
	printf("%d\n", ans);
}

 

posted @ 2019-06-18 17:30  uid001  阅读(155)  评论(0编辑  收藏  举报