TopCoder SRM 597 Div1 第3题

Problem Statement

 

Little Elephant from the Zoo of Lviv has a board with 2 rows and M columns. Each cell of the board must be painted in one of three colors: red, green, or blue.

The board is called magical if and only if it has the following properties:

  • No two adjacent cells share the same color. (Two cells are adjacent if they share an edge.)
  • Every 2x2 block contains at least one cell of each of the three colors.

You are given four ints M, R, G andB. Let X be the total number of different magical boards with 2 rows andM columns that contain exactlyR red cells, G green cells, and B blue cells. Return the value (X modulo 1,000,000,007).

Definition

 

Class:

LittleElephantAndBoard

Method:

getNumber

Parameters:

int, int, int, int

Returns:

int

Method signature:

int getNumber(int M, int R, int G, int B)

(be sure your method is public)

 

 

Constraints

-

M will be between 2 and 1,000,000, inclusive.

-

R, G and B will each be between 0 and 1,000,000, inclusive.

-

R+G+B will be equal to 2M.

Examples

0)

 

 

2

2

1

1

Returns: 4

The following 4 different magical boards are possible in this case:

1)

 

 

2

2

2

0

Returns: 0

No magical board is possible in this case.

2)

 

 

10

7

7

6

Returns: 496

 

3)

 

 

474

250

300

398

Returns: 969878317

类型:数论  难度:3.5

题意:给出M,R,G,B,2M=R+B+G,M代表一个2*M区域的列数,RGB分别代码红绿蓝格子的个数,要求:每个2*2格子中,三种颜色每种至少有一个格子,且相邻格子颜色不同,问有多少种排列方式

 

分析:比较复杂的一道数学问题。纠结了很多天。。

先将问题转化:

由于要满足上述要求,观察可得,每列的格子有6种状态,(RG,GB,BR),(RB,BG,GR),前三个看成一组,后三个看成一组,每组的三个状态可以互相转化,但是同种状态不相邻

可见第二组实际上是第一组每个情况上下互换位置所得,所以两组计算出的排列数相同,算出其中一种*2即可。

设第一组每个状态的个数为x,y,z,可得:x=M-B, y=M-R, z=M-G

那么问题转化为:给出三种颜色的球,个数为x,y,z,将其排成一行,要求同颜色球不相邻,问有多少种排列方式

 

考虑用插空法解决问题,设x>=y>=z,解法如下:

1、若y+z<x-1,返回0。即yz加起来都无法填补x个球的空隙,没有合法情况。

2、设yz所填的x的空隙数目为x1,x1=x-1,x,x+1,因为左右两边的空隙可放可不放,所以实际是四种情况,填左边x个空,右边x个空,中间x-1个空,填满x+1个空

遍历x1,累加结果,x1=x时,由于是两种情况,累加两次

3、在x1个位置中,设有y的位置为y1,y1<=y,那么先要从x1中选y1个位置,数量为c(x1,y1)

4、然后,还剩x1-y1个位置,这些位置每个位置都要放1个z,设为z1=x1-y1

5、那么,剩余的y(多出来的y)是y2=y-y1,将y2个y放到y1个位置上的数目是c(y1+y2-1,y1-1)(可以看成一个长度为y2+y1-1的序列,挑出y1-1个分割点)剩余的z是z2=z-z1,填充这些y的空隙需要y2个z,那么最后剩余的z就是z3=z2-y2

6、这z3个z能够放入的位置是每个yzyz...zy序列的两侧,这样的序列有y1个,那么z3个z的放法就是c(2y1,z3),至此,所有球分配完毕

由于M,R,G,B的范围为10^6,要在O(1)计算出c(m,n)%mod必须使用数组记录辅助数据,这段代码是参照别人的,就不细讲了。。

代码:

#include<string>
#include<cstdio>
#include<vector>
#include<cstring>
#include<map>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;

#define SWAP(x,y,t) (t)=(x),(x)=(y),(y)=(t)

const int mod = 1000000007;
const int maxn = 1000011;
int f[maxn],df[maxn],inv[maxn];


int multi(int x,int y)
{
	int tmp;
	if(x<y) SWAP(x,y,tmp);
	
	if(y==0) return 0;
	if(y==1) return x%mod;
	
	int half = multi(x,y/2)%mod;
	int ans = (half+half)%mod;
	
	if(y%2==0) return ans;
	else return (ans+x)%mod;
}

int mc(int a,int b)
{
	int tmp;
	if(b>a) SWAP(a,b,tmp);
	if(b<0) return 0;
	return (((long long)f[a]*df[b])%mod * df[a-b])%mod;
}

class LittleElephantAndBoard
{ 	 
	public:		
		int getNumber(int M, int R, int G, int B)
		{
			if(R>M || R<M/2 || G>M || G<M/2 || B>M || B<M/2)
				return 0;
			
			inv[1] = 1;
			for(int i=2; i<maxn; i++)
				inv[i] = ((long long)inv[mod%i] * (mod-mod/i))%mod;
			f[0] = df[0] = 1;
			for(int i=1; i<=maxn; i++)
			{
				f[i] = ((long long)f[i-1]*i)%mod;
				df[i] = ((long long)df[i-1]*inv[i])%mod;
			}
			
			int x,y,z,tmp;
			
			x = (G+R-B)/2;
			y = (G-R+B)/2;
			z = (R-G+B)/2;
			
			if(x<y) SWAP(x,y,tmp);
			if(x<z) SWAP(x,z,tmp);
			if(y<z) SWAP(y,z,tmp);
						
			int ans = 0;
			
			for(int i=-1; i<=1; i++)
			{
				int x1,y1,z1,ansx;
				x1 = x+i;
				y1 = y;
				if(x1==0) continue;
				if(x1<y1) y1=x1;
				
				ansx = 0;
				
				for(; ; y1--)
				{
					int y2,z2;
					z1 = x1-y1;
					y2 = y-y1;
					z2 = z-z1;
					
					if(y2>z2) break;
					
					int t1,t2,t3;
					t1 = mc(x1,y1)%mod;
					t2 = mc(y1+y2-1,y1-1)%mod;
					t3 = mc((y1+y1)%mod,z2-y2)%mod;
					t1 = multi(t1,t2)%mod;
					t1 = multi(t1,t3)%mod;
					ansx = (ansx + t1) % mod;
					
					//printf("x1 %lld y1 %lld z1 %lld y2 %lld z2 %lld t1 %lld t2 %lld t3 %lld\n",
					//	x1,y1,z1,y2,z2,t1,t2,t3);
				}
				
				ans = (ans+ansx)%mod;
				if(i==0) ans = (ans+ansx)%mod;
				ans %= mod;
			}
			ans = (ans+ans)%mod;
			return (int)ans;
		}
};

int main()
{
	//cout<<mc(1000000,500)<<endl;
	LittleElephantAndBoard t;
	cout<<t.getNumber(474, 250, 300, 398)<<endl;
	//cout<<t.getNumber(10,7,7,6)<<endl;
	//cout<<t.getNumber(3,1,2,3)<<endl;
}





 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值