状态压缩

算法就不解释了,直接上题

题目:http://codevs.cn/problem/1647/

这老经典的题了嚎~~~

首先,我们来看看m,(m<=10),

这样,我们就可以考虑动态规划

并且,如果我们把它划分成n层的话,那么第i层只跟第i-1层和第i-2层有关。

设第i层的决策状态为ki,则

a[i,ki,ki-1]=max(a[i-1,ki-1,ki-2]+num[ki])

对于每个状态,可以考虑状态压缩,

事先把每个状态都预处理一遍,储存在二进制数里

二进制数中'0'表示没有放置炮台,'1'表示放置了炮台。

再把每一层的地形存在二进制数里,'0'表示平原,'1'表示山地。

设map[i]为第i层的地形,

这样,对于第i(i>=3)层,首先枚举ki-2,使得k[i-2] & map[i-2]=0

再枚举ki-1,使得k[i-1] & map[i-1]=0且k[i-2] & k[i-1]=0,

再枚举ki,使得k[i] & map[i]=0且k[i] & k[i-1]=0且k[i] & k[i-2]=0(反过来也没关系啦)

代码上~:

#include <cstdio>
#include <iostream>
using namespace std;

int f[105][65][65],map[105],zbk[1050],num_zbk[1050];
char ch[150];

int main()
{
	int i,j,k,l,n,m,x,num,ans=0;
	
	freopen("a.txt","r",stdin);
	scanf("%d%d",&n,&m);
	
	for (i=1;i<=n;i++)
	{
		scanf("%s",&ch);
		for (j=1;j<=m;j++)
			if (ch[j-1]=='H')
				map[i]|=1<<(j-1);//map[i]记录第i行
	}
	num=0;
	for (i=0;i<=(1<<m)-1;i++)//i=0~111..1
	{
		if ((i&(i<<1)) || (i&(i<<2)))//不能有相邻1~2个1 
			continue;
		x=0;
		for (j=1;j<=m;j++)//找i的二进制1个数 
			if (i&(1<<(j-1)))
				x++;
		zbk[++num]=i;//zbk[]记录每行的二进制状态 
		num_zbk[num]=x;//状态下该行炮台个数 
	}
	
	//前面为预处理。。。 
	
	for (i=1;i<=num;i++)//第一行 
		if ((map[1]&zbk[i])==0)
			f[1][i][1]=num_zbk[i];
	//第二行
	for (i=1;i<=num;i++) //枚举两层状态:第二层 
		if ((map[2]&zbk[i])==0)
			for (j=1;j<=num;j++)//第一层 
				if ((map[1]&zbk[j])==0 && (zbk[i]&zbk[j])==0)
					f[2][i][j]=max(f[2][i][j],f[1][j][1]+num_zbk[i]);
					
	for (i=3;i<=n;i++)//3~n层 
		for (j=1;j<=num;j++)//第i层状态
			if ((map[i]&zbk[j])==0)
				for (k=1;k<=num;k++)//i-1层状态
					if ((map[i-1]&zbk[k])==0 && (zbk[j]&zbk[k])==0)
						for (l=1;l<=num;l++)//i-2层状态
							if ((map[i-2]&zbk[l])==0 && (zbk[k]&zbk[l])==0 && (zbk[l]&zbk[j])==0)
								f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+num_zbk[j]);
	
	for (i=1;i<=num;i++)
	{
		for (j=1;j<=num;j++)
			ans=max(ans,f[n][i][j]);
	}
	printf("%d",ans);
	return 0;
}

熟悉了算法如何写以后,再来看看这道题:
题目:http://www.gdfzoj.com/oj/contest/254/problems/3
看到n<=20我就试着做了下状压,发现可以实现,结果只有40。。。(第一次还忘了删文操。。。),才发现看错题了。。。
所以所以,看题!看题!!看题!!!(重要的事情说三遍)
思路不多说,直接上代码:
代码细节:
1)此处find≈lower_bound(pair不能用lower_bound)
找第一个x
2)注意pair定义,使用等。。。
#include <cstdio>
#include <algorithm>


using namespace std;

const int maxSize=23;
int a[maxSize],n;
pair <long long,long long> c[1<<maxSize];//写sort不用cmp,比map好 

long long find(long long x)
{
	int l=1,r=(1<<n)-1,mid;
	
	while (l!=r)
	{
		mid=(l+r)/2;
		if (c[mid].first>=x)//写等于可以找第一个x 
			r=mid;
		else
			l=mid+1;
	}
	return l;
}

int main()
{
	long long i,j,sum,ans=0;
	
	freopen("a.txt","r",stdin);
	scanf("%d",&n);
	for (i=0;i<n;i++)
		scanf("%d",&a[i]);
	
	for (i=1;i<(1<<n);i++)//预处理一遍和 
	{
		sum=0;
		for (j=0;j<n;j++)
			if (i&(1<<j))
				sum+=a[j];
		c[i]=make_pair(sum,i);
	}
	sort(c+1,c+i);//排序(按和) 
	
	for (i=1;i<(1<<n);i++)
	{
		sum=0;
		for (j=0;j<n;j++)//再找一遍和(排序后的,与前面预处理不重复)
			if (i&(1<<(j)))
				sum+=a[j];
		if (sum%2==1)	continue;
		for (j=find(sum/2);c[j].first==sum/2;j++)//二分找sum/2 
			if ((c[j].second&i)==c[j].second && c[j].second!=i)//sum/2 的状态属于sum状态子集 
			{
				ans++;
				break;//看题!!!!!!!!! 
			}
	}
	printf("%lld",ans);
	
	return 0;
}

T3:http://www.lydsy.com/JudgeOnline/problem.php?id=1087
这题跟第一题很像。。。
注意预处理、答案大小(long long)
#include <cstdio>
#include <algorithm>


using namespace std;

int zbk[1050],num_zbk[1050];
long long f[15][1050][150];//f[i,j,k]记录第i层为j状态,前i行放k个的方案数 
//要开ll,可以预估一下答案大小 
int main()
{
	int i,j,k,n,k1,num=0,x,l;
	long long ans;
	
	freopen("a.txt","r",stdin);
	scanf("%d%d",&n,&k1);
	for (i=0;i<(1<<n);i++)//预处理 
	{
		if (i&(i<<1))
			continue;
		x=0;
		for (j=0;j<n;j++)
			if (i&(1<<j))
				x++;
		zbk[++num]=i;	num_zbk[num]=x;
	}
//	for (i=1;i<n;i++)
//		for (j=1;j<=num;j++)
//			f[i][j][0]=1;//初始化 k=0时的情况 
	
	for (i=1;i<=num;i++)//第一层 
		if (num_zbk[i]<=k1)
			f[1][i][num_zbk[i]]=1;
	
	
	for (i=2;i<=n;i++)//第2~n层 
	{
		for (j=1;j<=num;j++)//第i层状态 
		{
			for (k=1;k<=num;k++)//第i-1层状态 
				if (!(zbk[j]&(zbk[k]<<1)) && !(zbk[j]&zbk[k])&&!(zbk[k]&(zbk[j]<<1)))
					for (l=num_zbk[j];l<=k1;l++)//循环'k'  
							f[i][j][l]+=f[i-1][k][l-num_zbk[j]];
		}
	}
	ans=0;
	for (i=1;i<=num;i++)
		ans+=f[n][i][k1];
	printf("%lld",ans);//记得打lld
	
	
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值