【原题地址】: 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);
}