题目大意:
从小写字母和大写字母里选出m个字母(可重复选择),组成一个单词,选字母时需要满足以下规则:
1.相邻字母ASCII值的差值<=32
2.选择的单词中至少有一对字母ASCII值的差值恰好等于32.
问你可以组成多少个不同的单词。
输出最后的结果mod 1000000007
其中:(2 ≤ m ≤ 109)
题解:
如果我们不考虑第二条规则和数据范围,可以推出如下递推式,设f[i][j]表示组成长为i的单词最后一个字母为j且相邻字母ASCII值的差值<=32的方案数,则有:
f[i][j]=sigma(f[i-1][k]),abs(ASCII(j)-ASCII(k))<=32。
为了保证第二条规则,我们再推一个递推式g[i][j]=sigma(g[i-1][k]),abs(ASCII(j)-ASCII(k))<=31,g[i][j]表示组成长为i的单词最后一个字母为j且相邻字母ASCII值的差值<=31的方案数。
那么最后答案便是这两个递推式分别在i=m时分别求和再相减。
但是这个题时间复杂度不够,想到了矩阵优化线性递推式,f[i][j],g[i][j]均是线性的且可以用滚动数组降维,仔细推敲发现可以用矩阵来优化这个递推式,这样这个题就完美的解决了。
时间复杂度O(52*52*52*logm),其中52是构造的矩阵的大小
实现时,为了程序易懂好写,采用了运算符重载。
#include<iostream>
#include<cstdlib>
#include<cstring>
using namespace std;
const long long mod=1000000007;
struct Matrix
{
long long a[55][55];
Matrix operator *(Matrix &b)
{
Matrix c;memset(c.a,0,sizeof(c.a));
for(int i=1;i<=52;i++)
for(int j=1;j<=52;j++)
{
for(int k=1;k<=52;k++)
c.a[i][j]=(c.a[i][j]+(a[i][k]*b.a[k][j])%mod)%mod;
}
return c;
}
Matrix operator ^(long long x)
{
Matrix e,b;
memset(e.a,0,sizeof(e.a));
for(int i=1;i<=52;i++)
e.a[i][i]=1;
memcpy(b.a,a,sizeof(a));
while(x!=0)
{
if(x&1)e=e*b;
b=b*b;
x=x>>1;
}
return e;
}
};
long long m;
int main()
{
int sec;
cin>>sec;
for(int i=1;i<=sec;i++)
{
cin>>m;
Matrix a,b;
memset(a.a,0,sizeof(a.a));
memset(b.a,0,sizeof(b.a));
for(int i=1;i<=52;i++)
{
for(int j=max(i-26,1);j<=min(i+26,52);j++)
a.a[j][i]=1;
}
for(int i=1;i<=52;i++)
{
for(int j=max(i-25,1);j<=min(i+25,52);j++)
b.a[j][i]=1;
}
a=a^(m-1);
b=b^(m-1);
long long ans=0,dec=0;
for(int i=1;i<=52;i++)
for(int j=1;j<=52;j++)
{
ans=(ans+a.a[i][j])%mod;
dec=(dec+b.a[i][j])%mod;
}
ans=(ans+mod-dec)%mod;
cout<<ans<<"\n";
}
return 0;
}