【垒骰子】蓝桥杯第六届省赛C/C++大学A组(动态规划+矩阵快速幂)

问题描述:
  赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
  经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
  我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
  atm想计算一下有多少种不同的可能的垒骰子方式。两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。由于方案数可能过多,请输出模 10^9 + 7 的结果。

「输入格式」
第一行两个整数 n m,n表示骰子数目。
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。

「输出格式」
一行一个数,表示答案模 10^9 + 7 的结果。

「样例输入」
  2 1
  1 2

「样例输出」
  544

「数据范围」
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms

  思路一:首先想到的是动态规划。我们一共要垒n层,那就一层层的垒。比如第一层一共有六种情况,分别是1,2,3,4,5,6面朝上,每一种都只有一种情况;第二层1朝上有多少种呢?我们可以将1朝上的骰子依次分别垒在第一层六个骰子上,如果4(1的对面是4)不与下面的骰子朝上的面冲突,就可以垒起来。
  因此,动态规划的表达式就出来了。我们用dp[i][j]表示表示第i层j面朝上一共有多少种可能。根据前面的分析,我们可以得到以下表达式:
【垒骰子】蓝桥杯第六届省赛C/C++大学A组(动态规划+矩阵快速幂)_第1张图片
c o n f l i c t [ j ] [ k ] = 0 conflict[j][k]=0 conflict[j][k]=0的意思是j与k不冲突。递推式很好理解,第i层j面朝上最多只有六种(如果不冲突),但是我们要排除掉冲突的情况。初始时dp[0][1 到 6]都是1。需要注意的是,我们最后输出的不是dp[n-1][1]累加到dp[n-1][6],而是它们的和乘上 4 n 4^n 4n,因为每一层每个筛子都可以转动。
代码:

#include
using namespace std;

int n,m;
const long long maxn = 1e7 + 5;
const long long mod = 1e9 + 7;
int op[7];
int conflict[7][7] = {0};
int dp[maxn][7];

void init() {
	op[1] = 4;
	op[4] = 1;
	op[2] = 5;
	op[5] = 2;
	op[3] = 6;
	op[6] = 3;
	for(int i = 1;i <= 6;i++) {
		dp[0][i] = 1;
	}
}

long long fastpow(int x, long long y) {
	long long res = 1, base = x % mod;
	while(y) {
		if(y & 1) {
			res = (res * base) % mod;
		}
		base = (base * base) % mod;
		y /= 2; 
	}
	return res;
}

int main() {
	cin>>n>>m;
	init();
	int a, b;
	for(int i = 0;i < m;i++) {
		cin>>a>>b;
		conflict[a][b] = conflict[b][a] = 1;
	}
	for(int i = 1;i < n;i++) {
		for(int j = 1;j <= 6;j++) {
			for(int k = 1;k <= 6;k++) {
				if(!conflict[op[j]][k]) {
					dp[i][j] += dp[i-1][k] % mod;
				}
			}
		}
	}
	int res = 0;
	for(int i = 1;i <= 6;i++) {
		res += dp[n-1][i] % mod;
	}
	res = res * fastpow(4, n);
	cout<<res;
	return 0;
}

  结果分析:该代码存在两个比较严重的问题:一是申请数组dp[maxn][7]时会报错,因为实在太大了;二是三层循环那里会超时,因此该方法不可行,需要换一种思路。

  思路二:我们换一种思路,我们构建一个冲突矩阵c,如果3与6冲突,则c[op[a]][b] = 0且c[op(b)][a]=0。c[op[a]][b] = 0的意思是a的对面与b是冲突的,也就是不能垒在一起。因为在上面动态规划的思路中,我们要算每一层各面朝上各有多少种可能,最后输出的是dp[n-1],dp[n-1]表示最后一层所有可能之和。我们容易知道dp[0]也就是第一层每个数都是1,也就是说dp[0]是一个全为1的列向量,我们用冲突矩阵c乘上dp[0],根据矩阵乘法规则,得到的也是一个6行1列的列向量,并且乘法结果其实就是dp[1]的值。那么则有:
在这里插入图片描述
  因此,我们只要算出冲突矩阵的n-次幂,然后再乘上一个全为1的列向量dp[0],最后将结果向量里面每个数相加即可。

代码:

#include
using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;
int n, m;
int op[7];

void init() {
	op[1] = 4;
	op[4] = 1;
	op[2] = 5;
	op[5] = 2;
	op[3] = 6;
	op[6] = 3;
}

struct matrix {
	ll mat[6][6];
	init(int flag) {
		if(flag == 1) {
			//memset(mat, 1, sizeof(mat));错误写法
			for(int i = 0;i < 6;i++) {
				for(int j = 0;j < 6;j++) {
					mat[i][j] = 1;
				}
			} 
		}else if(flag == 2) {   //单位矩阵 
			memset(mat, 0, sizeof(mat));
			for(int i = 0;i < 6;i++) {
				mat[i][i] = 1;
			}
		}else {
			memset(mat, 0, sizeof(mat));
		}
	}
}; 


void print(matrix x) {
	for(int i = 0;i < 6;i++) {
		for(int j = 0;j < 6;j++) {
			cout<<x.mat[i][j]<<' ';
		}
		cout << endl;
	}
}

matrix mul(matrix a,matrix b) {   //return a*b
	matrix c;  //0矩阵
	c.init(3);
	for(int i = 0;i < 6;i++) {
		for(int j = 0;j < 6;j++) {
			for(int k = 0;k < 6;k++) {
				c.mat[i][j] += ((a.mat[i][k] % mod) * (b.mat[k][j] % mod)) % mod;
				c.mat[i][j] %= mod;
			}
		}
	}
	return c;
}

matrix fast_pow(matrix A, int n) {   //return A^n % mod
	matrix B;  //单位矩阵
	B.init(2);
	while(n) {
		if(n & 1) {
			B = mul(B, A);
		}
		A = mul(A, A);
		n >>= 1; 
	}
	return B;
} 

long long fastpow(int x, long long y) {   //快速幂 
	long long res = 1, base = x % mod;
	while(y) {
		if(y & 1) {
			res = (res * base) % mod;
		}
		base = (base * base) % mod;
		y /= 2; 
	}
	return res;
}

int main() {
	cin >> n >> m;
	init();
	int a, b;
	matrix conflict;
	conflict.init(1);
	for(int i = 0;i< m;i++) {
		cin >> a >> b;
		conflict.mat[op[a] - 1][b - 1] = 0;  //冲突
		conflict.mat[op[b] - 1][a - 1] = 0; 
	}
	matrix final = fast_pow(conflict, n-1);
	int res = 0;
	for(int i = 0;i < 6;i++) {
		for(int j = 0;j < 6;j++) {
			res += final.mat[i][j];
		}
	}
	res *= fastpow(4, n);
	printf("%d", res);
	return 0;
}

你可能感兴趣的:(备战蓝桥杯,算法与数学泛谈)