【题解】[wikioi2873]日程表安排

题目地址:http://www.wikioi.com/problem/2873/

这题其实也不算难。。。只是我太弱,自己怎么想都想不出来,还是在Lz神牛的指导下,才把这道题写出来的。。

其实就是求格子染色方案数,看到这道题就会有一种是递推的感觉。

直接推情况很复杂,所以可以根据已安排的点来分段,每一段看做一个新的行,单独求出可行安排方案数,再由乘法原理向乘即是答案。

设dp[i][j]表示第i格染j色可行方案数,那么自然有dp[i][j]=∑dp[i-1][k] (k!=j),一行到递推最后,本行答案就是∑dp[本行长度][k] (k不与下一行第一个格子(以确定)颜色相同)

至于初始条件自己yy一下应该能想到。。。

还有一个问题。。。由于数据范围过大,直接递推明显会T。那么这里就可以用到矩阵乘法的来优化递推

由:

【dp[i][1]】  【0 1 1 1】  【dp[i-1][1]】

【dp[i][2]】=【1 0 1 1】* 【dp[i-1][2]】

【dp[i][3]】  【1 1 0 1】  【dp[i-1][3]】

【dp[i][4]】  【1 1 1 0】  【dp[i-1][4]】

得到:

【dp[i][1]】  【0 1 1 1】^i-1【dp[1][1]】

【dp[i][2]】=【1 0 1 1】    *【dp[1][2]】

【dp[i][3]】  【1 1 0 1】     【dp[1][3]】

【dp[i][4]】  【1 1 1 0】     【dp[1][4]】

然后再用矩阵乘法的快速幂即可快速求解

#include<cstdlib>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const long long P=1000000007;
int n,m;
int hehe[10][10]={{0},{0,0,1,1,1},{0,1,0,1,1},{0,1,1,0,1},{0,1,1,1,0}};
struct Mat
{
	int x,y;
	long long val[10][10];
	Mat()
	{
		memset(val,0,sizeof(val));
	}
	Mat operator *(const Mat &b)
	{
		Mat c;
		c.x=x;c.y=b.y;
		for(int i=1;i<=x;i++)
		{
			for(int j=1;j<=b.y;j++)
			{
				for(int k=1;k<=y;k++)
				{
					c.val[i][j]=(c.val[i][j]+(val[i][k]*b.val[k][j])%P)%P;
				}
			}
		}
		return c;
	}
}a,single;
struct D
{
	int pos,val;
}st[15];
Mat Mpow(const Mat &d,int c)
{
	if(c==0)return single;
	if(c==1)return d;
	Mat ret;
	ret=Mpow(d,c/2);
	ret=ret*ret;
	if(c&1)ret=ret*d;
	return ret;
}
long long ans=1;
long long calc(int x)
{
	long long ret=0;
	Mat C,ret1;
	C.x=4,C.y=1;
	for(int i=1;i<=4;i++)if(i==st[x].val||x==0)C.val[i][1]=1;
	Mat ss;
	ss=Mpow(a,st[x+1].pos-st[x].pos-1);
	ret1=ss*C;
	for(int i=1;i<=4;i++)if(i!=st[x+1].val)ret=(ret+ret1.val[i][1])%P;
	return ret;
}
void pre()
{
	a.x=a.y=4;
	for(int i=1;i<=4;i++)
	{
		for(int j=1;j<=4;j++)
		{
			a.val[i][j]=hehe[i][j];
		}
	}
	single.x=single.y=4;
	for(int i=1;i<=4;i++)
	{
		single.val[i][i]=1;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		char c[5];
		scanf("%d%s",&st[i].pos,c);
		st[i].val=c[0]-'A'+1;
	}
	pre();
	st[0].pos=1;st[m+1].pos=n+1;
	st[0].val=st[m+1].val=-1;
	if(st[1].pos>1||m==0)
	{
		ans=calc(0);
	}
	for(int i=1;i<=m;i++)
	{
		ans=(ans*calc(i))%P;
	}
	printf("%lld\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值