[蓝桥杯][2015年第六届真题]垒骰子(只是给自己复习用)

[蓝桥杯][2015年第六届真题]垒骰子

题目描述:

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

不要小看了 atm 的骰子数量哦~

输入
第一行两个整数 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
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。 注意: 所有依赖的函数必须明确地在源文件中 #include , 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。

思路:应该马上能想到用暴力解法,也就是深收、递归的做法。虽然明显会超时。。。应该有30%的分值

做法也非常简单,就是总数会等于最上面的数目(分别六种朝上的面)即6来乘以剩下的n-1颗骰子的情况。。。。
然后一直递归下去。。。

细心的朋友已经发现了,你不用转动吗?
所以我们最后的结果会乘以4n

不要忘记%MOD

#include<iostream>
using namespace std;
int op[7];
bool conflict[7][7];
const int MOD=100000007;
//define MOD 1000000007 

void init(){
	op[1]=4;
	op[4]=1;
	op[2]=5;
	op[5]=2;
	op[3]=6;
	op[6]=3;
}
int f(int up,int cnt){//up代表上一颗骰子的朝向,cut代表剩下的骰子数目 
	//出口
	if(cnt==0) return 1; //这里代表上一层朝上的是up,但是这一层我们没有骰子了
						//所以上一层朝上是up的数目是4*1 
	
	long long ant=0;
	for(int upp=1;upp<=6;upp++){
		if(conflict[op[up]][upp]==true) continue;
		ant=(ant+4*f(upp,cnt-1))%MOD;
	}
	return ant;
}
int main(){
	int n,m;
	cin>>n>>m;
	init();
	
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		conflict[x][y]=true;
		conflict[y][x]=true;
		
	}
	long long ans=0;
	for(int up=1;up<=6;up++){
		ans=((ans+4*f(up,n-1))%MOD);
	} 
	cout<<ans;
	
	return 0;
}

思路二:动态规划
刚才是从上往下来算,这次我们最下面一个骰子开始(其实都一样,我只是定义一个正反向,方便在计算骰子的“对面”时不搞混)

虽然还是不能拿满分,但是可以拿70%的分值

本质就是定义一个滚动数组来记录 “ 两层 ”值,0层是上一次放好的每面向上的个数,1层用来更新数据,给下次用。

代码及解释如下:

#include<iostream>
using namespace std;
int op[7];
long long dp[2][7];//滚动使用 
bool conflict[7][7];
const int MOD=100000007;
//define MOD 1000000007 

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

int main(){
	int n,m;
	cin>>n>>m;
	init();
	
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		conflict[x][y]=true;
		conflict[y][x]=true;
		
	}
	//以上输入已完成
	
	//初始化dp 
	for(int j=1;j<=6;j++){
		dp[0][j] = 1;
	} 
	
	int cur = 0;//用来使dp滚动 
	
	for(int level=2;level<=n; ++level){
		cur = 1-cur;
	//尝试将6个面放在当前这一层的朝上方向
	 	for(int j=1;j<=6;++j){
	 		dp[cur][j] = 0;//初始化以便下面更新
		//将与op[j]不冲突的上一次层格子里面的数目累加起来
			for(int i=1;i<=6;++i){
				if(conflict[op[j]][i]) continue;//i代表上一次格子的i朝上的累加总数
				dp[cur][j] = (dp[cur][j]+dp[1-cur][i]) % MOD;
			}	  
		 }
	} 
	long long sum=0;
	for(int k=1;k<=6;++k){
		sum = (sum+dp[cur][k])%MOD;	
	}
	
	//快速幂运算
	//求4的n次幂
	//将2^22转化为 2^16 * 2^0 * 2^4 * 2^1 * 2^0
	//22的二进制10110 
	long long ans = 1;
	long long tem = 4;
	long long p = n;
	
	while(p!=0){
		//位与运算,先将n和1转化为4*8个bit的二进制,如何1代表trueo
		//0代表false,然后就是正常的与运算了 
		if(p & 1==1) ans=(ans*tem)%MOD;
		tem=(tem*tem)%MOD;
		p >>= 1; //p=p/2; 有符号右移运算 p=p/2^1
				 //本质上是转化为2进制补码然后右移,符号位补齐 
	}
	 
	cout<<(sum*ans)%MOD;
	
	return 0;
}

思路三:说难想到的话其实也还好,当你在做动态规划的时候就会有这么点感觉,好像每次都是乘以一样的东西,然后我们把这东西定义为矩阵就行啦

但是问题是如果比赛已经将动态规划写好,还有时间改吗

代码及注释:

//跟动态规划一样,先放最下面一层
//冲突矩阵a[i][j]代表i+1向上时会不会与下面的j+1冲突 
#include<iostream>
//#include<bits/stdc++.h>
#include<map>
#include<vector>

using namespace std;
typedef long long LL;

int n,m;
map <int,int> op;
 
const int MOD=1000000007;
//define MOD 1000000007 

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

//定义全1矩阵 
struct M{
	LL a[6][6];
	M(){
		for(int i=0;i<6;i++){
			for(int j=0;j<6;j++){
				
				a[i][j]=1;
			}
		}
	}
};
//矩阵乘法 
M mMultiply(M m1,M m2){
	M ans;
	for(int i=0;i<6;i++){
		for(int j=0;j<6;j++){
			ans.a[i][j]=0;
			for(int k=0;k<6;k++){
				ans.a[i][j]=(ans.a[i][j]+m1.a[i][k]*m2.a[k][j])%MOD;
			}
		}
	}
	return ans; 
}
//矩阵M的k次幂
M mPow(M m,int k){
	M ans;
//初始化ans为单位矩阵
	for(int i=0;i<6;i++){
		for(int j=0;j<6;j++){
			if(i==j) 
				ans.a[i][j]=1;
			else
				ans.a[i][j]=0;
		} 
	}
	
	while(k!=0){
		if((k & 1)==1) {
			ans = mMultiply(ans,m);
		}
		m = mMultiply(m,m);
		k >>= 1;
	}
	return ans;
 
}

//快速幂运算
//求4的n次幂
//将2^22转化为 2^16 * 2^0 * 2^4 * 2^1 * 2^0
//22的二进
LL TM(LL tem,LL n){
	long long ans = 1;
	//long long tem = 4;
	long long p = n;
	
	while(p!=0){
		//位与运算,先将n和1转化为4*8个bit的二进制,如何1代表trueo
		//0代表false,然后就是正常的与运算了 
		if(p & 1==1) ans=(ans*tem)%MOD;
		tem=(tem*tem)%MOD;
		p >>= 1; //p=p/2; 有符号右移运算 p=p/2^1
				 //本质上是转化为2进制补码然后右移,符号位补齐 
	}
	return ans;	
}
	

int main(){
	init();
	cin>>n>>m;
	M cMatrix;//冲突矩阵
	for(int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b;
		//冲突矩阵a[i][j]代表i+1向上时会不会与下面的j+1冲突
		cMatrix.a[op[a]-1][b-1]=0;
		cMatrix.a[op[b]-1][a-1]=0;
	}
	
	M cM_n_1 = mPow(cMatrix,n-1);
	LL ans=0;
	
	for(int j=0;j<6;j++){
		for(int i=0;i<6;i++){
			ans=(ans+cM_n_1.a[j][i])%MOD;
		}
	}
	
	LL a =  TM(4,n);
	cout<<(ans*a)%MOD;
	return 0;	
	}
本题要掌握的有
  1. 快速幂
  2. 矩阵定义,及矩阵乘法
  3. 矩阵快速幂
  4. 当出现这种”树状“,考虑动态规划及矩阵幂
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值