1. 程式人生 > >2015藍橋杯省賽——疊骰子(第9題,DP)

2015藍橋杯省賽——疊骰子(第9題,DP)

賭聖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

DP(動態規劃):

將待求解的問題分解成若干個相互聯絡的子問題,先求解子問題,然後從這些子問題的解得到原問題的解;對於重複出現的子問題,只在第一次遇到的時候對它進行求解,並把答案儲存起來,讓以後再次遇到時直接引用答案,不必重新求解。


#include<stdio.h>
#include<memory.h>

#define mod 1000000007

bool compact[7][7]; //compact[i][j]=false:點數為i的面與點數為j的面存在衝突
const int parner[7]={0,4,5,6,1,2,3}; //parner[i]=j:點數為i的面,其對立面的點數為j

int main()
{
	long long n; //骰子的高度;long型最大表示9位十進位制數 
	int m; //衝突陣列
	int s1,s2;
	scanf("%lld %d",&n,&m);
	
	memset(compact,true,sizeof(compact));
	for(int i=0;i<m;i++){
		scanf("%d%d",&s1,&s2);
		compact[s1][s2]=compact[s2][s1]=false; //這裡想的挺好的 
	}
	
	long long dp[2][7]; //滾動陣列,節約空間;dp[i][j]:高度為i,頂面點數為j的疊骰子方案數;
			    //這裡忽略每個骰子可以四面轉的情況,最後把方案數乘以(4^i)即可 
	long long c=4;
	int e=0; //滾動標誌
	for(int i=1;i<7;i++)
		dp[e][i]=1;
 
	for(long long i=2;i<=n;i++)	{
		e = 1-e; //這裡的e一直1,0翻轉,實現陣列的滾動 
		c = (c*4)%mod; //c= 4^i % mod
		for(int j=1;j<7;j++){
			dp[e][j]=0;
			for(int k=1;k<7;k++)
				if(compact[parner[j]][k]) //點數為j的對立面與點數k可以緊貼 
					dp[e][j] += dp[1-e][k]; //一直累加前一個骰子的結果數 
			dp[e][j] %= mod;
		}
	}
	
	int count=0;
	for(int i=1;i<7;i++)
		count = (count+dp[e][i])%mod;
	count = (count*c)%mod;
	
	printf("%d\n",count);
	
	return 0;
	
}