Kiki & Little Kiki 2(矩阵快速幂(推理))

【题目来源】https://vjudge.net/problem/HDU-2276
【题意】
给出编号为0-n-1的灯的状态,0代表熄灭,1代表已开,并且这些灯有序围成一个圆,也就是说0号的左边是n-1号。
那么,给定一个游戏规则,每次操作之后,如果当前灯的左边灯状态是1,那么当前灯就要改变状态,如果是1,就要变成0,如果是0,反之。
那么经过m次操作之后,输出这些灯的状态。
分析样例:
0101111
编号为0:左边是1,0变化成为1;
编号为1:左边是是0,不变。(对比的是没变之前的)
编号为2:左边是1,0变成1;
编号为3:左边是0,不变。
编号为4:左边是1,变为0;
编号为5:左边是1,变为0;
编号为6:左边是1,变为0;
那么经过一次操作之后,得出的状态是1111000。
【思路】
看到那么大的变换次数,想到快速幂解决,但是怎么构建初始矩阵呢?
现在可以这么想:(拿第一组样例来说)
某个矩阵 乘以0101111得到 1111000
因为当前灯的状态与前一台有关系,所以会发现:
如果前一项是1,后一项是1,那么会得到 0
如果前一项是1,后一项是0,那么会得到 1
如果前一项是0,后一项是1,那么会得到 1
如果前一项是0,后一项是0,那么会得到 0;
发现像极了某种运算,那就是异或(^)。
那么就可以由这个去构建矩阵:
1 0 0 0 0 0 1
1 1 0 0 0 0 0
0 1 1 0 0 0 0
0 0 1 1 0 0 0
0 0 0 1 1 0 0
0 0 0 0 1 1 0
0 0 0 0 0 1 1

0
1
0
1
1
1
1

1
1
1
1
0
0
0
(只看第一列。。。)
结果第一个1意思就是编号为0的灯的状态为已开,模拟一下:
(1*0+0*1+0*0+0*1+0*1+0*1+1*1)%2=1。
对,其他的自行模拟。(比较懒。。。)
然后呢,要想到,一般的话,矩阵乘法是有三个for的,也就是O(n³)的复杂度,但是这里的n是100, 里面还有一些操作,所以,为了节省时间。引入一个叫做:循环矩阵的概念(自行点开。。。)
循环矩阵遵循代数运算法则。对于两个循环矩阵 A 与 B 来说,A + B 也是循环矩阵。A*B 也是循环矩阵,并且 A*B=B*A
既然循环矩阵可以由上一行的数字得到下一行,那么我们只需要在矩阵乘法里算出一行,递推出其他行,那么只需要O(n²)的时间复杂度。
还有,为了节省时间,矩阵乘法里1*0其他的等等都可以用&运算来代替。
【代码】

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<queue>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<limits.h>
#include<algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int mod=200907;
const double esp=1e-5;
typedef unsigned long long ll;
typedef long long LL;
int n,k;
char s[105];
struct mat
{
    int a[105][105];
} ans,base;
void print(mat t)
{
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            printf("%d ",t.a[i][j]);
        }
        printf("\n");
    }
}
mat operator*(mat t,mat tt)
{
    mat r;
    for(int i=1; i<=n; i++)
    {
        r.a[1][i]=0;
        for(int j=1; j<=n; j++)
            r.a[1][i]^=(t.a[1][j]&tt.a[j][i]);
    }
    for(int i=2; i<=n; i++)
        for(int j=1; j<=n; j++)
            r.a[i][j]=r.a[i-1][(j-2+n)%n+1];
    return r;
}
void pow_mat()
{
    mem(ans.a,0);
    for(int i=1; i<=n; i++)
        ans.a[i][i]=1;
    mem(base.a,0);
    for(int i=1; i<=n; i++)
    {
        base.a[i][i]=1;
        i==1?base.a[1][n]=1:base.a[i][i-1]=1;
    }
    while(k)
    {
        if(k&1)
            ans=ans*base;
        base=base*base;
        k>>=1;
    }
}
int main()
{
    while(~scanf("%d",&k))
    {
        scanf("%s",s);
        n=strlen(s);
        pow_mat();
        for(int i=1; i<=n; i++)
        {
            int x=0;
            for(int j=1; j<=n; j++)
                x^=(s[j-1]-'0')&ans.a[i][j];
            printf("%d",x);
        }
        printf("\n");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值