【题解】棋盘染色(ID : 20415)

非常牛的一道计数题啊。

下面是部分分:

  • 10 pts:爆搜枚举,但是我看错题了,没写出来。
  • [30,50] pts:dp,不会,没写,我是 dp 低手。

接下来是正解部分。
一定不要像我一样没看到相邻两个格子颜色不能相同,因为这至关重要。
我们发现两行非常不好做,想办法转化成 1 行,注意到同一列只有 RG GR BR RB GB BG 六种情况,且每两种正好没有包括 B GR,于是我们想到,用在这一列没有出现过的那种颜色去代表这一列的
两行
。这样,我们就有 m-r 个红色, m-b 个蓝色以及 m-g 个绿色。
为了方便,接下来仍然认为有 RRGG 以及 BB
假设我们最终搜出来有 x 种答案,那么我们可以把原棋盘每列两行颠倒得到新的 x 种合法方案,所以最后要乘 2
考虑如何在新的一行上统计答案,我们注意到,这些 R 把原序列划分成了若干个待填充的空段,且两两不相邻,具体的,如果有 RR

  • 若这个序列的首尾都是 R,那么一共有 R-1 段。
  • 如果首、尾有一个是 R,那么有恰好 R 段(注意首、尾要算两种情况)。
  • 如果首尾都不是 R,那么有 R+1 段。

所以我们外层套一个循环暴力枚举有多少段,设当前段数为 n

注意到待填的都是 GB ,同时他们两个必须间隔出现,所以段长的奇偶性会导致一些差别:

  • 如果段长是偶数,这一段 GB 的数量相同。
  • 如果是奇数,这一段 GB 的数量差 1 ,具体地,如果是 G 开头,那么 G1 ,否则反之。

我们继续设长为偶数的段有 x_0 个,长为奇数且以 G 开头的有 x_1 个,以 B 开头的是 x_2 个。

三元一次方程不好解,所以我们再用一层循环枚举 x_0\in[0,n] ,则 x_1x_2 显然满足如下关系:

\left\{ \begin{aligned} x_1 + x_2 & = n - x_0 \\ x_1 - x_2 & = G - B \end{aligned} \right. \iff \left\{ \begin{aligned} x_1 & = \frac{n - x_0 + G - B}{2} \\ x_2 & = \frac{n - x_0 + B - G}{2} \end{aligned} \right.

由于 nx_0GB 都是常量,那么 x_1x_2 也可以求出。
求出之后干点什么呢?
假设我们已经确定好这些空段的顺序,那么一共有多少种方案啊?
注意需要保证这些段填完之后都是非空的,所以我们给一个偶数段强行填入一个 GB(或者 BG),一个 x_1 段强行填入一个 G,一个 x_2 段强行填入一个 B,我们发现接下来我们只需要往所有段中捆绑填 BG 或者 GB 即可。
此时我们剩下 G-x_0-x_1GB-x_0-x_2B,当然这两个值应该是相等的。
接下来采取插板法,一共有 G-x_0-x_1 个绑包,分成 n 部分,可以有空(因为我们已经保证段非空了),则有 G-x_0-x_1+n-1\choose n-1 种方法。
所以单个排列的方案数是 G-x_0-x_1+n-1\choose n-1 的,那么如果在已知 x_0x_1x_2 的情况下,又有多少种排列呢?显然是 {n\choose x_0}\times{n-x_0\choose x_1}=\frac{n!}{x_0!x_1!x_2!} 的,因为注意到 x_0+x_1+x_2=n
最后还有一个容易被忽略的事情是,长为偶数的段可以任意选择 G 还是 B 开头,所以如果有 x_0 个偶数段的话,还要乘一个 2^{x_0}
场切的巨佬真的好牛好强。
实现需要预处理阶乘数组及其逆元,注意要更新下标为 0 时的数值。

#include <bits/stdc++.h> 
#define int long long 
#define f(i ,m ,n ,x) for (int i = (m) ;i <= (n) ;i += (x))
using namespace std ;
template < typename T > inline void read ( T &x ) {
	x = 0 ;
	bool flag (0) ;
	register char ch = getchar () ;
	while (! isdigit (ch)) {
		flag = ch == '-' ;
		ch = getchar () ;
	}
	while (isdigit (ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48) ;
		ch = getchar () ;
	}
	flag ? x = - x : 0 ;
} 
template < typename T ,typename ...Args > 
inline void read ( T &x ,Args &...tmp ) {
	read (x) ,read (tmp...) ;
}
const int N = 1e7 + 7 ,mod = 1e9 + 7 ;
int m ,r ,g ,b ,jc[N + 1] ,inv[N + 1] ,ans ;
namespace meloddd {
	inline int qpow (int base ,int p = mod - 2) {
		int ans = 1ll ;
		while (p) {
			if (p & 1ll) 
				(ans *= base) %= mod ;
			(base *= base) %= mod ;
			p >>= 1ll ; 
		}
		return ans ;
	}
	inline void pre () {
		jc[0] = 1ll ; // attention
		f (i ,1 ,N ,1) 
			jc[i] = jc[i - 1] * i % mod ;
	}
	inline void rep () {
		inv[N] = qpow (jc[N]) ;
		for (int i = N - 1 ;~ i ;i --) // ~ i but not i
			inv[i] = inv[i + 1] * (i + 1) % mod ;
	} 
	inline int C (int n ,int m) {
		return jc[n] * inv[n - m] % mod * inv[m] % mod ;
	} // 组合数公式展开一下
}
using meloddd :: qpow ;
using meloddd :: C ;
using meloddd :: pre ;
using meloddd :: rep ;
signed main () { 
	freopen ("board.in" ,"r" ,stdin) ;
	freopen ("board.out" ,"w" ,stdout) ;
	read (m ,r ,g ,b) ;
	pre () ,rep () ;
	if (max ({r ,g ,b}) > m) 
		return puts ("0") ,0 ;
	r = m - r ,g = m - g ,b = m - b ;
	if (r < g) swap (r ,g) ;
	if (g < b) swap (g ,b) ;
	if (r < b) swap (r ,b) ;
	for (int n : {r - 1 ,r ,r ,r + 1}) {
		if (n < 1) continue ;
		f (x0 ,0 ,n ,1) {
			static int x1 ,x2 ;
			x1 = (n - x0 + g - b) >> 1ll ;
			x2 = (n - x0 + b - g) >> 1ll ;
			if (x1 < 0 || x2 < 0 || x1 + x2 + x0 != n || x1 - x2 != g - b) continue ;
			int num = g - x0 - x1 ;
			if (num < 0) continue ;
			(ans += C (num + n - 1 ,n - 1) * jc[n] % mod * inv[x0] % mod 
			* inv[x1] % mod * inv[x2] % mod * qpow (2ll ,x0) % mod) %= mod ;
		}
	}
	cout << (ans << 1ll) % mod << '\n' ;
	return 0 ;
}
1 个赞

好奇怪的码风啊,应该使用VS写的(

1 个赞

其实是 dev

1 个赞

dev不会加空格的吧

1 个赞

我自己打出来的()

1 个赞

如果真是你打出来的话我只能说码风好奇怪(

1 个赞

()可读性还好吧()

1 个赞

不用函数封装,加上宏定义for我感觉怪怪的

我有一些奇怪爱好,比如我喜欢把自己的用户名加进去,还有我喜欢 ::,另外我从小不会写 for ()见谅()

码风能与某位写class的管理员有一战之力

()

这是 P1817 棋盘染色 - 洛谷 | 计算机科学教育新生态 的简化版吗

这道题也是神了,题解里面全都是打表。

是的,一道水紫题

应该是加强版(

不对应该是简化版,你的代码在https://www.luogu.com.cn/problem/P1817 上肯定会T