作业:
C: Q老师的考验
Q老师 对数列有一种非同一般的热爱,尤其是优美的斐波那契数列。
这一天,Q老师 为了增强大家对于斐波那契数列的理解,决定在斐波那契的基础上创建一个新的数列 f(x) 来考一考大家。数列 f(x)
定义如下:当 x < 10 时,f(x) = x; 当 x ≥ 10 时,f(x) = a0 * f(x-1) + a1 * f(x-2) + a2* f(x-3) + …… + a9 * f(x-10),ai 只能为 0 或 1。
Q老师 将给定 a0~a9,以及两个正整数 k m,询问 f(k) % m 的数值大小。
聪明的你能通过 Q老师 的考验吗?
Input
输出文件包含多组测试用例,每组测试用例格式如下:第一行给定两个正整数 k m。(k < 2e9, m < 1e5)
第二行给定十个整数,分别表示 a0~a9。
Output
对于每一组测试用例输出一行,表示 f(k) % m 的数值大小。
这道题目是一道矩阵快速幂的模板题目,关键在于如何构造状态转移矩阵,
将F[n]…F[n-9]作为一个10行一列的矩阵A,他由F[9]…F[0]这个十行一列的矩阵C乘以一个10行10列的矩阵B的n-9次幂得来。B矩阵的第一行输入a0-a9,这一行乘以C矩阵得到F[10],以此类推乘以n-9次B矩阵就能得到F[n],B矩阵的其他行对角线的下方位置是1。这样我们完成了B矩阵的构建。带入快速幂模板即可解得结果。
注意最后要把该矩阵的第一行乘以C矩阵得到最终结果F[n]。
#include
using namespace std;//hdu 1757
const int N=10;
int k,m;
struct Matrix{
int x[10][10];
Matrix operator *(const Matrix&t)const
{
Matrix ret;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
ret.x[i][j]=0;
for(int k=0;k<N;k++){
ret.x[i][j]+=x[i][k]* t.x[k][j]%m;
ret.x[i][j]%=m;
}
}
}
return ret;
}
Matrix(){
memset(x,0,sizeof(x));}
Matrix(const Matrix&t){
memcpy(x,t.x,sizeof(x));}
};
Matrix qpow(Matrix a,int x ){
Matrix ret;
for(int i=0;i<10;i++)
ret.x[i][i]=1;
while(x){
if(x&1)ret=ret*a;
a=a*a;
x>>=1;
}
return ret;
}
int main()
{
while(cin>>k>>m)
{
Matrix a;
for(int i=0;i<10;i++)cin>>a.x[0][i];
for(int i=1;i<10;i++)a.x[i][i-1]=1;
if(k<10)printf("%d\n",k%m);
else{
a=qpow(a,k-9);
int ans=0;
for(int i=0;i<10;i++)
ans=ans+a.x[0][i]*(9-i)%m;
printf("%d\n",ans%m);
}
}
return 0;
}
D - Q老师染砖
Description
衣食无忧的 Q老师 有一天突发奇想,想要去感受一下劳动人民的艰苦生活。
具体工作是这样的:
有 N 块砖排成一排染色,每一块砖需要涂上红、蓝、绿、黄这 4 种颜色中的其中 1 种。且当这 N块砖中红色和绿色的块数均为偶数时,染色效果最佳。
为了使工作效率更高,Q老师 想要知道一共有多少种方案可以使染色效果最佳,你能帮帮他吗?
Input
第一行为 T,代表数据组数。(1 ≤ T ≤ 100)
接下来 T 行每行包括一个数字 N,代表有 N 块砖。(1 ≤ N ≤ 1e9)
Output
输出满足条件的方案数,答案模 10007。
这道题也是一道矩阵快速幂的题目,但是涉及到DP的思想,难点在寻找状态转移方程,而这道题一个状态是不够的,因此令
A[i]表示i个格子红绿均为偶数的方案数目
B[i]表示i个格子红绿均为奇数的方案数目
C[i]表示i个格子红绿有一个为偶数的方案数目
因此可以得到状态转移方程:
A[i] = 2 * A[i-1] + C[i-1]
B[i] = 2 * B[i-1] + C[i-1]
C[i] = 2 * A[i-1] + 2 * B[i-1] + 2 * C[i-1]
于是初始化进行幂运算的矩阵B为三个方程的系数即可,由于矩阵可以行变换所以行顺序无关。
#include
#include
#include
using namespace std;//Poj 3734
const int N=3;
const int m=10007;
struct Matrix{
int x[3][3];
Matrix operator *(const Matrix&t)const
{
Matrix ret;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
ret.x[i][j]=0;
for(int k=0;k<N;k++){
ret.x[i][j]+=x[i][k]* t.x[k][j]%m;
ret.x[i][j]%=m;
}
}
}
return ret;
}
Matrix(){
memset(x,0,sizeof(x));}
Matrix(const Matrix&t){
memcpy(x,t.x,sizeof(x));}
};
Matrix qpow(Matrix a,int x ){
Matrix ret;
for(int i=0;i<N;i++)
ret.x[i][i]=1;
while(x){
if(x&1)ret=ret*a;
a=a*a;
x>>=1;
}
return ret;
}
int main()
{
int T;cin>>T;
while(T--)
{
int k;cin>>k;
Matrix a;
a.x[0][0]=2;a.x[0][1]=0;a.x[0][2]=1;
a.x[1][0]=0;a.x[1][1]=2;a.x[1][2]=1;
a.x[2][0]=2;a.x[2][1]=2;a.x[2][2]=2;
a=qpow(a,k);
printf("%d\n",a.x[0][0]);
}
return 0;
}
E - Q老师度假
忙碌了一个学期的 Q老师 决定奖励自己 N 天假期。 假期中不同的穿衣方式会有不同的快乐值。
已知 Q老师 一共有 M 件衬衫,且如果昨天穿的是衬衫 A,今天穿的是衬衫 B,则 Q老师 今天可以获得 f[A][B] 快乐值。在 N 天假期结束后,Q老师 最多可以获得多少快乐值?
Input
输入文件包含多组测试样例,每组测试样例格式描述如下:第一行给出两个整数 N M,分别代表假期长度与 Q老师 的衬衫总数。(2 ≤ N ≤ 100000, 1 ≤ M ≤ 100)
接下来 M 行,每行给出 M 个整数,其中第 i 行的第 j 个整数,表示 f[i][j]。(1 ≤ f[i][j] ≤ 1000000)测试样例组数不会超过 10。
Output
每组测试样例输出一行,表示 Q老师 可以获得的最大快乐值。
先写出这道题目的状态 [][] 表示第 i 天,穿的衣服为 j 所获得的快乐值总和。
所以可以得到:
[][]=max([−1][]+[][]),1≤≤
但是由于M≤100,所以复杂度很高,解不出来。所以我们用矩阵快速幂优化复杂度。先构建矩阵A=BC,其中A为i+1天的快乐值,其中有M行分别表示第i+1天穿第j件衣服的快乐值,C矩阵和A矩阵一样表示第i天的情况。B矩阵由输入提供。
这里的矩阵乘法,不再是相乘然后求和的传统矩阵乘法,而是相加取最大值的新定义的乘法。
还有一个要注意的点:
矩阵快速幂的快速幂之前要给幂运算的结果矩阵设为单位矩阵。
其中单位矩阵由乘法定义。正常乘法使用的是线性代数中的正常的单位矩阵E,而这道题的单位矩阵是0矩阵。
#include
#include
#include
typedef long long ll;
using namespace std;//T-shirt(2018江苏邀请赛I题)
ll n,m;
struct Matrix{
ll f[110][110];
Matrix(){
memset(f,0,sizeof(f));}
Matrix operator *(Matrix & mat){
Matrix ret;
for(ll i=1;i<=m;i++){
for(ll k=1;k<=m;k++){
for(ll j=1;j<=m;j++){
ret.f[i][j]=max(ret.f[i][j],mat.f[i][k]+f[k][j]);
}
}
}
return ret;
}
};
Matrix qpow(Matrix a,ll b){
Matrix ret;
//这个自定义乘法的单位元是0矩阵 不是单位矩阵
while(b){
if(b&1) ret=ret*a;
a=a*a;
b>>=1;
}
return ret;
}
int main()
{
while(scanf("%lld%lld",&n,&m)!=EOF){
Matrix mat;
for(ll i=1;i<=m;i++){
for(ll j=1;j<=m;j++){
scanf("%lld",&mat.f[i][j]);
}
}
mat=qpow(mat,n-1);
ll ans=0;
for(ll i=1;i<=m;i++){
for(ll j=1;j<=m;j++){
ans=max(ans,mat.f[i][j]);
}
}
printf("%lld\n",ans);
}
return 0;
}
实验题:猫睡觉问题
一道很复杂的模拟题,大意是:一只猫,规定它每天能睡就睡,一睡就必须睡不小于A个小时,而他又不能清醒超过B个小时。现在给出一个时间表。这个表里规定了它必须清醒的时间。问能否符合规定。能输出结果,不能输出NO。
输入的格式是 hh:mm-hh:mm ,这道题乍一看很懵逼,不知道怎么处理时间,但是其实时间是可以化为分钟的。所以我们用分钟来表示,只在输出的时候改变格式,这样就能比较方便的处理时间段。
然后我们按时间段排序。如果间隔时间大于a,则先休息。把休息的时间段记录起来。否则把下一段时间归并到当前时间段。同时判断当前时间段是否大于b,是则输出No。如果最后一个时间段的结束时间与第一个时间的开始时间的间隔大于等于a,说明可以分割。否则需要查看当前时间段是否可以连接到第一个时间段前面。
注意:猫要休息……
#include
#include
#include
using namespace std;
const int maxn = 20;
const int day = 24*60;
struct node
{
int s;
int e;
}pt[maxn+5];
vector<node>v;
bool cmp(const node &a, const node &b)
{
return a.s<b.s;
}
void show(node temp)
{
printf("%d%d:%d%d-%d%d:%d%d\n", temp.s/600, temp.s/60%10, temp.s%60/10, temp.s%10, temp.e/600, temp.e/60%10, temp.e%60/10, temp.e%10);
}
int main()
{
int a, b;
int n;
int i;
bool fail;
char s[20];
node temp, x;
while(scanf("%d %d", &a, &b)!=EOF)
{
a *= 60;
b *= 60;
scanf("%d", &n);
for (i=1; i<=n; i++)
{
scanf("%s", s);//hh:mm-hh:mm 2 5 8
pt[i].s = (s[0]-'0')*600+(s[1]-'0')*60+(s[3]-'0')*10+(s[4]-'0');
pt[i].e = (s[6]-'0')*600+(s[7]-'0')*60+(s[9]-'0')*10+(s[10]-'0');
if (pt[i].e<pt[i].s) pt[i].e += day;
}
sort(pt+1, pt+n+1, cmp);
v.clear();
fail = false;
for (i=1; i<=n; i++)
{
if (pt[i].e-pt[i].s+1>b)
{
fail = true;
break;
}
}
if (!fail)
{
temp.s = pt[1].s;
temp.e = pt[1].e;
for (i=1; i<n; i++)
{
if (pt[i+1].s-1-temp.e>=a)
{
x.s = temp.e+1;
x.e = pt[i+1].s-1;
if (x.s!=x.e) v.push_back(x);
temp.s = pt[i+1].s;
temp.e = pt[i+1].e;
}
else
{
temp.e = pt[i+1].e;
if (temp.e-temp.s+1>b)
{
fail = true;
break;
}
}
}
}
if (!fail)
{
if (pt[1].s+day-1-temp.e>=a)
{
x.s = (temp.e+1)%day;
x.e = (pt[1].s-1+day)%day;
if (x.s!=x.e) v.push_back(x);
}
else if (v.size()>0 && (v[1].s-1+day)%day-temp.s+1<=b)
{
}
else fail = true;
}
if (fail || v.size()==0)
printf("No\n");
else
{
printf("Yes\n%d\n", v.size());
sort(v.begin(), v.end(), cmp);
for (i=0; i<v.size(); i++)
show(v[i]);
}
}
return 0;
}