• 牛客 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
    ", ans);
    }
    
  • 相关阅读:
    Lambda表达式
    多态之美
    集合那点事
    程序员艺术家
    MySQL:如何导入导出数据表和如何清空有外建关联的数据表
    Ubuntu修改桌面为Desktop
    shutil.rmtree()
    SCP命令
    kickstart
    数据哈希加盐
  • 原文地址:https://www.cnblogs.com/uid001/p/11046460.html
Copyright © 2020-2023  润新知