Distinct Paths 293B/299D(搜索+剪枝)

【原题地址】: Distinct Paths 299D

【题目大意】:

给定一个 \(n*m\) 大小的方格,有的格子需要染色,有的格子有初始颜色,颜色种类为 \(1 \to n\) ,问有多少种染色方案可以让每一条从左上 \((1,1)\) 到右下 \((n,m)\) 的路径不经过相同颜色的点

【题解】:

题目上的 \(1000*1000\) 范围就是用来吓人的,其本质还是一个搜索,因为 \(n+m-1>k\) 时方案数为 \(0\)
\(1≤k≤10\) 所以我们可以特判,当 \(n+m-1>k\) 输出 \(0\)
我们用 f[x][y] 来表示当前 \((x,y)\) 可染的色的状态

\[f[x][y]=f[x][y-1]|f[x-1][y] \]

按照从左到右,从上到下
每次枚举当前染的色
剪枝
① 可行性剪枝,当 \((x,y)\) 可染的色的总数 \(<n-x+m-y+1\) 就返回
因为无论如何都没法保证不重复染色到 \((n,m)\)
② 对称性剪枝,当多个颜色是第一次在当前点被染(包括初始被染的色),那么无论染哪种颜色方案数都是一样的,只需要记录一下,一次 \(dfs\) 的值,后面就可以直接用了

如下代码 cnt[] 用来统计当前每种颜色用的次数
详细见代码注释

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=30,mod=1e9+7;
int f[maxn][maxn],cnt[maxn];
int map[maxn][maxn];
int n,m,k,ans=0;
int dfs(int x,int y)
{
	if(x==n+1)return 1;//处理到终点
	int to_x,to_y,mark=0,ret=0,last=0,num=0;
	f[x][y]=f[x-1][y]|f[x][y-1];//处理不可以染的颜色
	if(y+1>m)to_x=x+1,to_y=1;
	else to_x=x,to_y=y+1;//处理下一步需要染色的位置
	for(int i=1;i<=k;i++)
	if(!(f[x][y]&(1<<i-1)))num++;//求出可染的颜色数
	if(num<n-x+m-y+1)return 0;//可行性剪枝
	if(!map[x][y])//当前点没有初始颜色
	{
		for(int i=1;i<=k;i++)
		if(!(f[x][y]&(1<<i-1)))
		{
			if(cnt[i]==0)//i号颜色是第一次出现
			{
				if(mark)ret+=last,ret%=mod;//如果之前求过值就加上
				else //没求过,就dfs并且记录
				{
					mark=1;
					cnt[i]++;
					f[x][y]|=1<<i-1;
					last=dfs(to_x,to_y);
					f[x][y]^=1<<i-1;
					cnt[i]--;
					ret+=last;
					ret%=mod;
				}
				continue;
			}	
			cnt[i]++;
			f[x][y]|=1<<i-1;
			ret+=dfs(to_x,to_y);
			f[x][y]^=1<<i-1;
			cnt[i]--;
			ret%=mod;
		}	
	}
	else 
	{
		if(!(f[x][y]&(1<<map[x][y]-1)))
		{
			f[x][y]|=1<<map[x][y]-1;
			ret+=dfs(to_x,to_y);
			f[x][y]^=1<<map[x][y]-1;
			ret%=mod;
		}
	}
	return ret;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	if(n>=20||m>=20)
	{
		printf("0\n");
		return 0;
	}//特判
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	scanf("%d",&map[i][j]),cnt[map[i][j]]++;//初始颜色需要记录
	ans=dfs(1,1);
	printf("%d\n",ans);
}
posted @ 2018-04-11 21:30  Harry_bh  阅读(516)  评论(0)    收藏  举报