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);
}

转载于:https://www.cnblogs.com/Harry-bh/p/8798883.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值