CodeVS2873 日程表安排

链接

  http://codevs.cn/problem/2873/

题解

  首先,用M个点把这N天分开之后,各个段都是独立的,因此一定可以每一段分别算出然后乘起来得到总的方案数。

  对于一个左端没有限制,右端有颜色限制的段,方案数明显是3^L,其中L是这一段的长度。一个右端没有限制,左端有限制的段是一样的。

  因此先把最左和最右两端的答案统计下来。然后考虑中间那些段。

  对于一个中间的段,其左右都有不能选择的颜色,单独拿出长度为L的一段,设f[i][j]表示考虑了前i个位置,最后一个位置涂第j种颜色的方案数。

  易知f[i][1]=f[i-1][2]+f[i-1][3]+f[i-1][4],其余的同理,初始状态f[1][不能涂的颜色]=0,f[1][能涂的颜色]=1,最后把f[L][能涂的颜色]加起来就好了。到这里已经很显然了,矩阵快速幂。

  以上就是这道题的一般思路。

  对于这道题目,它有一个很好的性质,那就是它的转移矩阵只有对角线上是0,其余位置都是1。这样就产生了很好的性质。

  

  上面展示了最左端不能填A的情况,容易发现,每一列的和总是3的幂,第i列的和是3^i。我们要求的就是最后一列的和减去不能涂的颜色的那个数。比如一共有6个格子,最后一个格子不能填B,那答案就是729-182。而182是可以算出来的,183=3^6-(3^6+3)/4。

  怎样知道这个格子中是几呢?

  不难发现,最开始不能涂的那个颜色的值总是和其它颜色不同,而且随着下标的奇偶的变化,这种颜色的方案数总是周期性地比其它格子大1或少1,而减去的那个数,既和下标的奇偶性有关,又和开始不能涂的颜色有关。

  通过归纳总结,容易总结出规律:

  当开头和结尾不能涂的颜色相同时,如果L是偶数,ans=3^n-(3^n + 3)/4;如果L是奇数,ans=3^n-(3^n + 3)/4

  当开头和结尾不能涂的颜色不同时,如果L是奇数,ans=3^n-(3^n -  1)/4;如果L是奇数,ans=3^n-(3^n + 1)/4

  这样就不需要矩阵了。

  这里似乎可以出题呢。当矩阵除了对角线是0,其余位置是1时,上述规律总是满足。

代码

//矩阵快速幂
#include <cstdio>
#include <algorithm>
#define ll long long
#define mod 1000000007
using namespace std;
ll trans[5][5], one[5][5], N, M, ans;
struct str{ll pos, col;}s[20];
inline bool cmp(str A, str B){return A.pos<B.pos;}
void mat_cpy(ll a[][5], ll b[][5])
{
	ll i, j;
	a[0][0]=b[0][0], a[0][1]=b[0][1];
	for(i=1;i<=a[0][0];i++)for(j=1;j<=a[0][1];j++)a[i][j]=b[i][j];
}
void mat_mult(ll a[][5], ll b[][5], ll c[][5])
{
	ll i, j, k, t[5][5]={0};
	t[0][0]=a[0][0];t[0][1]=b[0][1];
	for(i=1;i<=t[0][0];i++)
		for(j=1;j<=t[0][1];j++)
			for(k=1;k<=a[0][1];k++)
				t[i][j]=(t[i][j]+a[i][k]*b[k][j])%mod;
	mat_cpy(c,t);
}
void mat_pow(ll a[][5], ll b, ll ans[][5])
{
	ll t[5][5];
	for(mat_cpy(ans,one),mat_cpy(t,a);b;b>>=1,mat_mult(t,t,t))
		if(b&1)mat_mult(ans,t,ans);
}
ll pow(ll a, ll b)
{
	ll ans, t;
	for(ans=1,t=a;b;b>>=1,t=t*t%mod)
		if(b&1)ans=ans*t%mod;
	return ans;
}
void init()
{
	ll i, j;
	char c;
	scanf("%lld%lld",&N,&M);
	for(i=1;i<=M;i++)
	{
		scanf("%lld %c",&s[i].pos,&c);
		s[i].col=c-'A'+1;
	}
	sort(s+1,s+M+1,cmp);
	one[0][0]=one[0][1]=4;
	for(i=1;i<=4;i++)one[i][i]=1;
	trans[0][0]=trans[0][1]=4;
	for(i=1;i<=4;i++)for(j=1;j<=4;j++)if(i!=j)trans[i][j]=1;
}
void work()
{
	ll i, f[2][5], t[5][5], l;
	f[0][0]=1,f[0][1]=4;
	if(M==0){ans=4*pow(3,N-1)%mod;return;}
	ans=pow(3,s[1].pos-1)*pow(3,N-s[M].pos)%mod;
	for(i=1;i<M;i++)
	{
		l=s[i+1].pos-1-s[i].pos;
		if(l==0)continue;
		f[1][1]=f[1][2]=f[1][3]=f[1][4]=1;f[1][s[i].col]=0;
		mat_pow(trans,l-1,t);
		mat_mult(f,t,f);
		ans=ans*(f[1][1]+f[1][2]+f[1][3]+f[1][4]-f[1][s[i+1].col])%mod;
	}
}
int main()
{
	init();
	work();
	printf("%lld",ans);
	return 0;
}

//找规律
#include <cstdio> 
#include <algorithm>
#define ll long long
#define mod 1000000007ll
using namespace std;
ll ans, N, M, inv4, num[20];
struct str
{
	ll pos, col;
}s[20];
inline bool cmp(str A, str B)
{
	return A.pos<B.pos;
}
ll pow(ll a, ll b)
{
	ll ans, t;
	for(ans=1,t=a;b;t=t*t%mod,b>>=1)if(b&1)ans=ans*t%mod;
	return ans;
}
void input()
{
	ll i;
	char c;
	scanf("%lld%lld",&N,&M);
	for(i=1;i<=M;i++)
	{
		scanf("%lld %c",&s[i].pos,&c);
		s[i].col=c-'A'+1;
	}
	sort(s+1,s+M+1,cmp);
}
void solve()
{
	ll i, l, t;
	if(M==0){ans=4*pow(3,N-1)%mod;return;}
	ans=pow(3,s[1].pos-1)*pow(3,N-s[M].pos)%mod;
	for(i=1;i<M;i++)
	{
		l=s[i+1].pos-1-s[i].pos;
		if(s[i].col==s[i+1].col)
			t= l&1 ? pow(3,l)-(pow(3,l)-3)*inv4 : pow(3,l)-(pow(3,l)+3)*inv4;
		else
			t= l&1 ? pow(3,l)-(pow(3,l)+1)*inv4 : pow(3,l)-(pow(3,l)-1)*inv4;
		ans=t%mod*ans%mod;
	}
}
inline void exgcd(ll a, ll b, ll &x, ll &y)
{
	if(!b){x=1;y=0;return;}
	ll xx, yy;
	exgcd(b,a%b,xx,yy);
	x=yy, y=xx-a/b*yy;
}
int main()
{
	ll y;
	input();
	exgcd(4,mod,inv4,y);
	inv4=(inv4+mod)%mod;
	solve();
	printf("%lld",(ans+mod)%mod);
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值