N车问题(状态压缩dp)

Description

  在一个N*N的棋盘上,放置N个车,某些位置不能放,一旦(x,y)格子放置一个车后,其同行、同列不能再放车了(即棋盘上的车不能相互攻击),请你合法的放置方案数 mod 123456789。

Input

  第一行一个整数N,表示棋盘大小。接下来的若干行,每行两个整数:x y,表示,棋盘的x行y里不能放车,以0 0结束输入。

Output

  一个整数,合法放置方案 mod 123456789 的结果。

分析题目,最终放的情况只能是每列一个,因此用一个集合S表示当前每列是否放置了车。设一个f(i,S)表示第i行放完之后,每列的放置情况S。则可以得到状态转移方程:f(i,S)=\sum^{j\epsilon S} f(i-1,S-(j)),这里的j必须是集合S里的。尽管比暴搜好很多,理论上时间复杂度为O(n^2*2^n),但N=20时依然超时。进一步分析,当枚举到第i行时,算上第i行一共放了i个棋子。因此,我们需要的集合S也应该正好包含i个元素。于是预处理计算出每个集合包含了多少元素(bit数组),可以避免很多无谓的循环。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=123456789;
int Map[21][21],f[21][1100000],bit[1100000];
int main()
{
//	freopen("in.txt","r",stdin);
	int N,i,j,k,x,y,ALL;
	
	cin>>N;//记录不能放的位置 
	memset(Map,0,sizeof(Map));
	while(cin>>x>>y&&x) Map[x][y]=1;
	
	memset(f,0,sizeof(f));
	ALL=(1<<N)-1;//每一行的最大集合 
	for(i=1;i<=N;i++) if(!Map[1][i]) f[1][(1<<(i-1))]=1;//第一行的方案 
	
	memset(bit,0,sizeof(bit));//预处理,计算每个集合包含的元素个数 
	for(i=0;i<=ALL;i++)
		for(j=1;j<=N;j++)
			if(i&(1<<(j-1)))
				bit[i]+=1;
	
	for(i=2;i<=N;i++)//从第2行开始 
	for(k=0;k<=ALL;k++) if(bit[k]==i)//加上这个bit优化快如飞 
	{
		for(j=1;j<=N;j++) if(k&(1<<(j-1))&&!Map[i][j])//如果j放的情况包含在k集合里 
		{
			x=k-(1<<(j-1));//x集合是k集合去掉j的情况
			f[i][k]=(f[i][k]+f[i-1][x])%mod;//i-1行摆成了x集合,第i行加一个j形成k集合 
		}
	}
	
	cout<<f[N][ALL];//第N行,放完所有车
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/81165504
今日推荐