向前走莫回头❤

【校内互测】Sunshine’s string(merge) (状压dp)

Sunshine’s string(merge.cpp)

【问题描述】

无聊的Sunshine大爷开始研究字符串。他找来了一个长度为n的01字符串,并制定了一些规则:每次可以将k个字符合并,得到一个新的字符并获得一定的分数。追求完美的宇宙金牌爷想知道他最多能获得多少分数。

【输入格式】

第一行两个整数n和k,分别表示字符串的长度和每次合并的长度。

第二行n个0或1的字符。

接下来2^k行,第i行表示长度为k的01串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符和对应的第i种方案对应获得的分数。

【输出格式】

一个整数表示能获得的最大分数。

【样例输入】

3 2

1 0 1

1 10

1 10

0 20

1 30

【样例输出】

40

【数据规模及约定】


————————————————————————————————————————————

【题解】【状压dp】

【发现k比较小,考虑状压
记f[i][j][S]表示把i~j这一段消除成S这个状态能获得的最大分数,转移的时候只考虑将右边的一段消到只有一个字符,然后加到左边去。并且f[i][j][S]可以直接转移到f[i][j][c[S]]。
复杂度O(n^3*2^k)
看起来复杂度很高但是可以发现转移其实很少】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long 
using namespace std;
ll f[310][310][1<<8];
int c[1<<8],w[1<<8],a[310],n,k;
int main()
{
	freopen("merge.in","r",stdin);
	freopen("merge.out","w",stdout);
	int i,j;
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;++i) scanf("%d",&a[i]);
	for(i=0;i<(1<<k);++i) scanf("%d%d",&c[i],&w[i]);
	memset(f,128,sizeof(f));
	ll INF=f[0][0][0];
	for(i=n;i;--i)
	 for(j=i;j<=n;++j)
	  {
	  	if(i==j) {f[i][j][a[i]]=0; continue;}
	  	int len=j-i;  ll t,now;
	  	while(len>=k) len-=k-1;
	  	for(int l=j;l>i;l-=k-1)
	  	 for(int h=(1<<len)-1;~h;h--)
	  	  if((now=f[i][l-1][h])!=INF)
	  	   {
	  	   	if((t=f[l][j][0])!=INF) f[i][j][h<<1]=max(f[i][j][h<<1],t+now);
	  	   	if((t=f[l][j][1])!=INF) f[i][j][h<<1|1]=max(f[i][j][h<<1|1],t+now);
			 }
		if(len==k-1)
		 {
		 	ll g[2]; g[0]=g[1]=INF;
		 	for(int h=(1<<k)-1;~h;h--)
		 	 if(f[i][j][h]!=INF) g[c[h]]=max(g[c[h]],f[i][j][h]+w[h]);
		 	f[i][j][0]=g[0]; f[i][j][1]=g[1];  
		 }
	  }
	ll ans=0;
	for(i=(1<<k)-1;~i;--i) ans=max(ans,f[1][n][i]);
	printf("%lld\n",ans);
	return 0;
}


posted @ 2016-08-04 20:58  lris0-0  阅读(72)  评论(0编辑  收藏  举报
过去的终会化为美满的财富~o( =∩ω∩= )m