蓝桥杯省赛编程大题——骰子问题

作为第二道编程大题,难度也没有想象中那么难。只怪我当时没有认真的静下心来仔细的理解题目意思。连样例都没有看懂,高数课闲来无事,便静下来认真想了一下。

大致的题目意思是给你n个骰子,让你堆起来,并且告诉你有一些面是不能靠在一起的。问你总共有多少总方案数。(骰子规定1和4相对,2和5相对,3和6相对)

输入n m 表示骰子的个数和不能靠在一起的个数。

输入m行,每行两个数a b 表示a和b两面不能靠在一起。

输出一行方案数,数据较大请模100000000+7;

样例输入:

2 1

1 2

样例输出:

544

60%的数据是n<100的

100%的数据是n<10^9, m<36;

对于60%的数据,dp的思路是很好想的。比赛的时候也已经想到了。但是因为没有想清楚样例,从而把题目想复杂了。写了一堆的map balabala~

正确理解题目意思后,我们可以发现要继续在n-1颗骰子上添加一颗骰子,我们需要知道原来的第n-1颗骰子的顶面和当前骰子的底面。因为骰子的底面可以由顶面知道,所以dp数组需要用一维记录顶面就好。

dp[i][j] : 放置好前 i 个骰子后并且顶面点数是j的翻案数。

此时我们只要考虑第 i 个骰子和第 i-1 个骰子不会冲突就行。

dp[i][j] += dp[i-1][k] *4; (状态k与状态j的两个骰子不会冲突,乘4的原因是骰子以j为顶面的方案有4种);

dp[1][j] = 4; (初始化每个顶面向上都有4种方案);

计算骰子的对面的点数。观察课发现,每个面的点数与对面的点数都相差3,即 对面的点数 = (当前面的点数 + 3) % 6; 当然点数3的时候要进行特判。

由此,我们可以通过60%的数据。

对于100%的数据,数量级一下增大到了10^9。确实挺吓人。但要知道数量级如此大的话复杂度一定不会是O(n),而前面我们可以看出来是一个递推式,那么,拿一个学长的话来说“傻子都知道是矩阵快速幂”。(弱弱说一句,比赛时我也想得到是矩阵快速幂可解,奈何题意没理解正确,dp没写出来,快速幂也就没想了。不然还指不定推不推的出来呢)。

由前面的dp方程我们可以知道,答案的矩阵肯定是一个1*6的矩阵。那么要用矩阵快速幂,另一个矩阵怎么也得是6*6的吧?

其实我们可以根据转移可知共有6*6种转移方式。而是否可以转移正好可以用6*6的矩阵表示。

那么现在有一个答案矩阵

A[] = {4,4,4,4,4,4}

A[i] 表示以i点数为顶点的方案数

还有一个转移矩阵初始为

B[][] = {

4,4,4,4,4,4,

4,4,4,4,4,4,

4,4,4,4,4,4,

4,4,4,4,4,4,

4,4,4,4,4,4,

4,4,4,4,4,4

}

B矩阵的意思是B[i][j]元已i点数为顶点的骰子柱在放一个以j为顶点的骰子的方案数。这样和1*6的矩阵相乘的话得到的 _A[6] 矩阵 依旧表示各顶点的方案数。

对于每组不可相靠在一起的情况,我们只需要在B数组的相应位置置为0就好

于是要求解 |A*B^(n-1)| 就行。

#include <cstring>
#include <iostream>
using namespace std;

const long long MOD = 1000000000 + 7;

int n, m;
bool vis[7][7];

int solve_01 () {
	long long dp[110][7];

	memset(dp, 0, sizeof(dp));
	for (int i=1; i<7; i++) {
		dp[1][i] = 4;
	}

	for (int i=2; i<=n; i++) {
		for (int j=1; j<=6; j++) {
			for (int k=1; k<=6; k++) {

				int tmp = (j + 3) % 6;
				if (tmp == 0) tmp = 6;

				if (vis[k][tmp]) {
					dp[i][j] = (dp[i][j] + dp[i-1][k] * 4) % MOD;
				}
			}
		}
	}

	long long sum=0;
	for (int i=1; i<=6; i++) {
		sum = (sum + dp[n][i]) % MOD;
	}
	return sum % MOD;
}

struct Node {
	long long a[7][7];

	Node () {
		memset(a, 0, sizeof(a));
		for (int i=0; i<7; i++) {
			a[i][i] = 1;
		}
	}

	void show() {
		cout << "=====Node=====" << endl;
		for (int i=1; i<=6; i++) {
			for (int j=1; j<=6; j++) {
				cout << a[i][j] << " ";
			}
			cout << endl;
		}
	}
};

Node mul (Node a,Node b){
	Node c;
	for (int i=1; i<=6; i++){
		for (int j=1; j<=6; j++){
			c.a[i][j]=0;
			for (int v=1; v<=6; v++){
				c.a[i][j] += (a.a[i][v] * b.a[v][j]) % MOD;
			}
			c.a[i][j]%=MOD;
		}
	}
	return c;
}

Node power(Node s, int n) {
	Node res;
	while (n > 0) {
		if (n&1) res = mul(res, s);
		s = mul(s, s);
		n>>=1;
	}
	return res;
}

int solve_02 () {

	Node s;
	memset(s.a, 0, sizeof(s));
	for (int i=1; i<=6; i++) {
		for (int j=1; j<=6; j++) {
			if (vis[i][j]) {
				int tmp = (j + 3) % 6;
				if (tmp == 0) tmp = 6;
				s.a[i][tmp] = 4;
			}
		}
	}

	Node rec = power(s, n-1);

	long long ans = 0;
	for (int i=1; i<=6; i++) {
		for (int j=1; j<=6; j++) {
			ans = (ans + rec.a[i][j] * 4) % MOD;
		}
	}
	return ans%MOD;
}

int main () {
	memset(vis, true, sizeof(vis));

	cin >> n >> m;

	int a, b;
	for (int i=0; i<m; i++) {
		cin >> a >> b;
		vis[a][b] = false;
		vis[b][a] = false;
	}

	int res = solve_01();

	int rec = solve_02();

	cout << res << " " << rec << endl;
	return 0;
}

时间: 2024-12-20 21:14:51

蓝桥杯省赛编程大题——骰子问题的相关文章

蓝桥杯省赛总结

3.28号下午查到了成绩,还行,考到B组省赛一等奖,可以进入决赛. 成绩的结果还是很意外的,因为自己没怎么准备,试题有的题就是瞎写的,只看了官网的两个辅导视频.剩下的懒了 就没看... 面对这电脑,看着题目,才意识到自己的不足之处.以前总是急功近利,想着学几门编程语言,现在才真正明白,语言只是个工具,自己要懂得利用一门编程语言解决实际的问题,并且考虑到优化提高的办法,这样才能提高自己能力,而不是会多少编程语言. 今天记录这些,就是激励自己,以一颗平静的心去学习.在记录,总结,展望中不断地提高自己

2018 蓝桥杯省赛 B 组模拟赛(一)

2018 蓝桥杯省赛 B 组模拟赛(一) A.今天蒜头君带着花椰妹和朋友们一起聚会,当朋友们问起年龄的时候,蒜头君打了一个哑谜(毕竟年龄是女孩子的隐私)说:“我的年龄是花椰妹年龄个位数和十位数之和的二倍”. 花椰妹看大家一脸懵逼,就知道大家也不知道蒜头君的年龄,便连忙补充道:“我的年龄是蒜头君个位数和十位数之和的三倍”. 请你计算:蒜头君和花椰妹年龄一共有多少种可能情况? 提醒:两位的年龄都是在 [10,100)[10,100) 这个区间内. 题解: 暴力枚举 answer: 1 代码如下: #

第七届蓝桥杯省赛7:剪邮票

第七届蓝桥杯省赛7:剪邮票 如[图1.jpg], 有12张连在一起的12生肖的邮票. 现在你要从中剪下5张来,要求必须是连着的. (仅仅连接一个角不算相连) 比如,[图2.jpg],[图3.jpg]中,粉红色所示部分就是合格的剪取. 请你计算,一共有多少种不同的剪取方法. 请填写表示方案数目的整数. 注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字. 解决思路:先将所有五个一组的情况遍历,然后用广度优先判断是否连通. 我在选五个一组的时候是用的 for 先选出第几个邮票,然后将其

2015年蓝桥杯省赛B组C/C++(试题+答案)

首先说,这次我是第二次参加蓝桥杯(大学里最后一次),可这次去连个三等都没拿到,有些心灰意冷,比上一次还差, 当时看到成绩出来的时候有些失落,但是跌倒了,再站起来继续跑就可以了.可能是状态不好吧,纯属自我安慰. 接下来我把今年的题目又重新做了一遍,写下了这篇博客,如果也有需要探讨答案的,希望可以有帮助. 第一题: 第1题:统计不含4的数字 题目大意 统计10000至99999中,不包含4的数值个数. 解题分析: 第一种解法: 数学方法,这种是在网上看到的一种解法: 最高位除了0.4不能使用,其余8

第六届蓝桥杯2015-省赛-C语言大学B组 个人题解

题目连接:http://course.baidu.com/view/2d86a6c1960590c69fc37622.html 1,奖券数目52488 #include <iostream> using namespace std; bool isOK(int a) { while(a) { if(a%10==4) return false; a /= 10; } return true; } int main() { int ans = 0; for(int i=10000; i<=9

第八届蓝桥杯省赛题解

一直以来,自己的科技类竞赛实在太少了,确实是自己的问题,从大学没有对自己有一个明确的规划,现在再回过头去恶补很多的知识点.悔当初,甚至怀疑自己,却已经来不及,最近因为某些人某些事让自己心情变得十分差,把自己弄得狼狈不堪. 今年是第一次参加蓝桥杯比赛,原本寒假就打算好好复习的,一直没有静下心来准备,正式做题是在比赛前3天.校区是在绵阳师范墨家小区,还以为头一天就要赶过去,后面接到学院老师的电话说一起跟着大部队过去,于是就在早上6点起来,看了会算法,吃早餐后就随着校车去了. 这次去参赛的都是比较厉害

算法笔记_212:第七届蓝桥杯软件类决赛真题(Java语言B组)

目录 1 愤怒小鸟 2 反幻方 3 打靶 4 路径之谜 5 碱基 6 圆圈舞 前言:以下代码仅供参考,若有错误欢迎指正哦~ 1 愤怒小鸟 愤怒小鸟 X星球愤怒的小鸟喜欢撞火车! 一根平直的铁轨上两火车间相距 1000 米 两火车 (不妨称A和B) 以时速 10米/秒 相对行驶. 愤怒的小鸟从A车出发,时速50米/秒,撞向B车, 然后返回去撞A车,再返回去撞B车,如此往复.... 两火车在相距1米处停车. 问:这期间愤怒的小鸟撞 B 车多少次? 注意:需要提交的是一个整数(表示撞B车的次数),不要

算法笔记_208:第六届蓝桥杯软件类决赛真题(Java语言A组)

目录 1 胡同门牌号 2 四阶幻方 3 显示二叉树 4 穿越雷区 5 切开字符串 6 铺瓷砖   前言:以下代码仅供参考,若有错误欢迎指正哦~ 1 胡同门牌号 标题:胡同门牌号 小明家住在一条胡同里.胡同里的门牌号都是连续的正整数,由于历史原因,最小的号码并不是从1开始排的. 有一天小明突然发现了有趣的事情: 如果除去小明家不算,胡同里的其它门牌号加起来,刚好是100! 并且,小明家的门牌号刚好等于胡同里其它住户的个数! 请你根据这些信息,推算小明家的门牌号是多少? 请提交该整数,不要填写任何多

算法笔记_206:第五届蓝桥杯软件类决赛真题(Java语言A组)

目录 1 海盗分金币 2 六角幻方 3 格子放鸡蛋 4 排列序数 5 幂一矩阵 6 供水设施   1 海盗分金币 有5个海盗,相约进行一次帆船比赛. 比赛中天气发生突变,他们被冲散了. 恰巧,他们都先后经过途中的一个无名的荒岛,并且每个人都信心满满,觉得自己是第一个经过该岛的人. 第一个人在沙滩上发现了一堆金币.他把金币分成5等份.发现刚好少一个金币.他就从自己口袋拿出一个金币补充进去,然后把属于自己的那份拿走. 第二个到达的人也看到了金币,他也和第一个人一样,把所有金币5等分,发现刚好缺少一个