• bzoj 2669 题解(状压dp+搜索+容斥原理)


    这题太难了...看了30篇题解才整明白到底咋回事...

    核心思想:状压dp+搜索+容斥

    首先我们分析一下,对于一个4*7的棋盘,低点的个数至多只有8个(可以数一数)

    这样的话,我们可以进行一个状压,把所有的低点压进来

    然后我们从小到大枚举所有数,转移即可

    记状态f[i][j]表示到了第i个数,低点的状态为j的方案数

    那么在转移的时候,有两个转移方向:

    ①.如果第i个数放在低点上,那么我们可以枚举所有的低点k,如果低点没有在状态里,有:

    dp[i][j|(1<<k)]+=dp[i-1][j]

    ②.如果第i个数放在高点上,那么我们需要枚举所有可以使用的高点,所谓可以使用的高点就是指的某一高点周围没有未使用的低点(原因:我是从小往大枚举的所有数,如果我在一个高点上放了一个数而他附近却有低点没有放上,那么这个低点会变得比这个高点高,这就是不合法的了。)

    但是如果每次都枚举,时间复杂度是会爆炸的,所以我们需要进行一个预处理num[i],表示i状态下有多少个高点可以使用

    最后的答案就是dp[n*m][1<<cnt-1]

    可是这个答案是正确的吗?

    我们发现,如果仅仅是这么枚举,很容易出现一种情况:某个点本来是高点,但是由于随意的放置使得这个点变成了低点

    所以我们需要解决掉这个问题

    怎么解决?

    很显然,我们只需求出把每个可能被放错的高点真正作为低点的方案数,然后用总方案数减掉这个方案数就可以了。

    可是,如果我们分别去减,由于减的方案数还要像上面那样dp出来,所以会产生更多的重复(即高点1和高点2同时放错)

    所以我们需要利用容斥,即

    0个高点反转-1个高点反转+2个高点反转....

    (反转指反转状态)

    那么怎么反转?

    dfs!

    贴代码:

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    #define ll long long
    #define mode 772002
    using namespace std;
    bool used[10][10];
    bool maps[10][10];
    int n,m;
    ll dp[30][(1<<15)+5];
    char ch[10];
    int dir[10][2]={{0,0},{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1}};
    int temp[30][2];
    int num[(1<<15)+5];
    ll ans=0;
    bool check(int x,int y)
    {
    	if(x>0&&x<=n&&y>0&&y<=m)
    	{
    		return 1;
    	}
    	return 0;
    }
    ll get_dp()
    {
    	int cnt=0;
    	memset(dp,0,sizeof(dp));
    	memset(used,0,sizeof(used));
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++)
    		{
    			if(maps[i][j])
    			{
    				used[i][j]=1;
    				temp[++cnt][0]=i;
    				temp[cnt][1]=j;
    			}
    		}
    	}
    	for(int i=0;i<=(1<<cnt)-1;i++)
    	{
    		int tot=0;
    		memset(used,0,sizeof(used));
    		for(int j=1;j<=cnt;j++)
    		{
    			if(!((1<<(j-1))&i))
    			{
    				if(!used[temp[j][0]][temp[j][1]])
    				{
    					used[temp[j][0]][temp[j][1]]=1;
    					tot++;
    				}
    				for(int t=1;t<=8;t++)
    				{
    					int x=temp[j][0]+dir[t][0];
    					int y=temp[j][1]+dir[t][1];
    					if(check(x,y)&&!used[x][y])
    					{
    						used[x][y]=1;
    						tot++;
    					}
    				}
    			}
    		}
    		num[i]=n*m-tot;
    	}
    	dp[0][0]=1;
    	for(int i=1;i<=n*m;i++)
    	{
    		for(int j=0;j<=(1<<cnt)-1;j++)
    		{
    			dp[i][j]+=dp[i-1][j]*max(num[j]-i+1,0)%mode;
    			dp[i][j]%=mode;
    			for(int k=1;k<=cnt;k++)
    			{
    				if(!((1<<(k-1))&j))
    				{
    					dp[i][j|(1<<(k-1))]+=dp[i-1][j];
    					dp[i][j|(1<<(k-1))]%=mode;
    				}
    			}
    		}
    	}
    	return dp[n*m][(1<<cnt)-1];
    }
    void dfs(int x,int y,int typ)
    {
    	if(x==n+1)
    	{
    		if(typ%2)
    		{
    			ans-=get_dp();
    			ans%=mode;
    		}else
    		{
    			ans+=get_dp();
    			ans%=mode;
    		}
    		return;
    	}
    	if(y==m+1)
    	{
    		dfs(x+1,1,typ);
    		return;
    	}
    	dfs(x,y+1,typ);
    	if(!maps[x][y])
    	{
    		bool flag=0;
    		for(int i=1;i<=8;i++)
    		{
    			int st=x+dir[i][0];
    			int ed=y+dir[i][1];
    			if(maps[st][ed])
    			{
    				flag=1;
    				break;
    			}
    		}
    		if(!flag)
    		{
    			maps[x][y]=1;
    			dfs(x,y+1,typ+1);
    			maps[x][y]=0;
    			return;
    		}
    	}
    }
    int main()
    {
    	int cot=0;
    	while(scanf("%d%d",&n,&m)!=EOF)
    	{
    		cot++;
    		bool u=0;
    		memset(maps,0,sizeof(maps));
    		for(int i=1;i<=n;i++)
    		{
    			scanf("%s",ch);
    			for(int j=0;j<m;j++)
    			{
    				if(ch[j]=='.')
    				{
    					maps[i][j+1]=0;
    				}else
    				{
    					maps[i][j+1]=1;
    					if(maps[i-1][j+1]||maps[i][j])
    					{
    						u=1;
    					}
    				}
    			}
    		}
    		if(u)
    		{
    			printf("Case #%d: 0
    ",cot);
    			continue;
    		}
    		ans=0;
    		dfs(1,1,0);
    		printf("Case #%d: %lld
    ",cot,(ans%mode+mode)%mode);
    	}
    	return 0;
    } 
  • 相关阅读:
    .NET开源项目
    关于微信号的校验
    java 中关于synchronized的通常用法
    关于java 定时器的使用总结
    新的博客已经启用,欢迎大家访问(402v.com)
    Hadoop综合大作业
    hive基本操作与应用
    理解MapReduce计算构架
    熟悉HBase基本操作
    第三章 熟悉常用的HDFS操作
  • 原文地址:https://www.cnblogs.com/zhangleo/p/10764229.html
Copyright © 2020-2023  润新知