【luogu U137467】飞行棋(dfs)(搜索剪枝)

飞行棋

题目链接:luogu U137467

题目大意

给你一个 n*m 的网格,然后上面已经有一些位置有 1~k 的数。
然后你要把网格填满 1~k 的数,问你有多少种填的方案满足不会有任何一条左上到右下的路径经过了数字相同的点。
然后定义这些路径都只能往右或往下走。

思路

你先考虑看一下一个点不能和哪些点同一个颜色。

在这里插入图片描述

观察一下可以得到,粉色的点 ( i , j ) (i,j) (i,j) 不能跟红色的点同一个颜色。
具体来讲就是 ( 1 , 1 ) (1,1) (1,1) ( i , j ) (i,j) (i,j) 的前缀矩阵, ( i , j ) (i,j) (i,j) ( n , m ) (n,m) (n,m) 的后缀矩阵。

那我们想了一下没有什么办法,考虑用玄学算法——暴搜剪枝!!!
那我们可以从上到下,从左到右填,那你每次填了之后你都可以维护出每个颜色再前缀矩阵的是否出现。

然后由于你后缀矩阵里的点都没有填,只有本身有的数,那你就可以提前用本身有的数跑后缀矩阵,然后简单的判断一下。

然后这样就是普通的 dfs 了,接下来考虑剪枝。


先给一个小剪枝,就是如果当前这个点可以选的颜色小于它到终点还需要的颜色,那就没有解。
(这里可以选的颜色不能看后缀矩阵,因为你后缀矩阵中的数你可能后面走的时候会用到,所以只能看已经填好的前缀矩阵)

然后给一个大剪枝。就是如果你当前选的一个数是在这个矩阵中第一次出现,那这些选了就是第一次出现的数的贡献是一样的,那我们只需要计算一次,然后记录下来这个值。(不要全局,每个位置是不一样的)
然后如果你再枚举的时候有找到了这些点,就不需要再 dfs 下去了。

然后就可以过啦!

代码

#include<cstdio>
#define ll long long
#define mo 1000000007

using namespace std;

int n, m, k, a[15][15], sum[15][15][15];
int hounum[15][15][15], num[15];
ll ans;

void clac(int x, int y) {//计算前面的矩阵每个颜色的出现次数
	for (int i = 1; i <= k; i++)
		sum[x][y][i] = sum[x - 1][y][i] + sum[x][y - 1][i] - sum[x - 1][y - 1][i];
}

ll work(int x, int y) {
	if (y > m) {
		y = 1; x++;
	}
	if (x > n) {
		return 1;
	}
	if (a[x][y]) {
		clac(x, y);
		if (sum[x][y][a[x][y]] || hounum[x][y][a[x][y]] > 1) return 0;
		sum[x][y][a[x][y]]++;
		ll re = work(x, y + 1);
		sum[x][y][a[x][y]]--;
		return re;
	}
	
	ll rem = -1, re = 0;
	clac(x, y);
	int ok = k;
	for (int i = 1; i <= k; i++)
		if (sum[x][y][i]) ok--;
	if (n + m - x - y > ok) return 0;//用前面算出可以走的颜色个数不足以走到终点
	for (int i = 1; i <= k; i++) {
		if (sum[x][y][i] || hounum[x][y][i]) continue ;
		sum[x][y][i]++;
		num[i]++;
		if (num[i] == 1) {//第一次出现的数字贡献相同,可以一次算出,后面都用
			if (rem == -1) rem = work(x, y + 1);
			re = (re + rem) % mo;
		}
		else re = (re + work(x, y + 1)) % mo;
		num[i]--;
		sum[x][y][i]--;
	}
	return re;
}

int main() {
	scanf("%d %d %d", &n, &m, &k);
	
	if (n + m - 1 > k) {
		printf("0"); return 0;
	}
	
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			scanf("%d", &a[i][j]);
			if (a[i][j]) num[a[i][j]]++, hounum[i][j][a[i][j]] = 1;
		}
	
	for (int i = n; i >= 1; i--)//计算后面的矩阵每个颜色的出现次数(因为后面的都没填,所以就只需要看本身就有的数)
		for (int j = m; j >= 1; j--) {
			for (int kk = 1; kk <= k; kk++)
				hounum[i][j][kk] += hounum[i + 1][j][kk] + hounum[i][j + 1][kk] - hounum[i + 1][j + 1][kk];
		}
	
	ans = work(1, 1);
	printf("%lld", ans);
	
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值