zoukankan      html  css  js  c++  java
  • 矩阵与线性代数学习笔记

    矩阵简介

    矩阵(Matrix)可以看作一个二维数组,就是一个有n行m列的填满数的数字阵。一个矩阵可以写作:

    A=a11a21an1a12a22an2a1ma2manm

    关于矩阵论的最基础理论可以参考1,这本书从需要的基础知识讲起,通过线性方程和向量空间、线性无关组、包和维度、矩阵的秩等概念逐步深入矩阵理论,之后引出行列式的计算与构造。在第三章还给出了近世代数的一些基本概念。对与OI来说,这本书的内容(从知识上)应该已经足够。

    求解线性方程

    通过直观的高斯消元法可以在O(n3)的时间复杂度内求解含有n个方程的方程组。本质的说,这是一个解空间的过程,也就是求解标准基下的一个向量再矩阵所代表的基下的解。也就是说,我们可以通过方程组建立模型来解决一些问题。

    bzoj1013球形空间产生器

    题意:给你n+1个n维空间中的点,求他们所在的球的球心。

    我们大概都还记得平面上不同线的三点确定一个圆。这是为什么呢?设三点坐标为(x0,y0),(x1,y1),(x2,y2),圆心坐标(x,y),半径为r,那么有:

    (x0x)2+(y0y)2=r2(x1x)2+(y1y)2=r2(x2x)2+(y2y)2=r2

    也就是:

    x202xx0+x2+y202yy0+y20=r2x212xx1+x2+y212yy1+y21=r2x222xx2+x2+y222yy2+y22=r2

    由于r2x2很不优雅,我们通过(2)(1),(3)(2)得到两组新方程,不难发现其为线性方程组。C为常数,这里省略不写。

    {2(x0x1)x+2(y0y1)y=C12(x0x2)x+2(y0y2)y=C1

    当且仅当2(x0x1)2(y0y1) 2(x0x2)2(y0y2)0时,方程有唯一解。用高斯消元可以O(n3)解出。发现其恰好为三点不共线的条件(从叉积考虑)。

    n和n+1只要仿照这里就可以了。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 20;
    double A[N][N];
    
    int n;
    
    void solve()
    {
        for (int i = 1; i <= n+1; i++) {
            if (A[i][i] == 0) {
                int k = i;
                while (A[k][i]) k++;
                swap(A[k], A[i]);
            }
            for (int j = i+1; j <= n; j++) {
                for (int k = i+1; k <= n+1; k++)
                    A[j][k] -= A[i][k]*(A[j][i]/A[i][i]);
                A[j][i] = 0;
            }
        }
        for (int i = n; i > 1; i--) {
            for (int j = 1; j < i; j++)
                A[j][n+1] -= A[i][n+1]*(A[j][i]/A[i][i]), A[j][i] = 0;
        }
        for (int i = 1; i < n; i++)
            printf("%.3f ", A[i][n+1]/A[i][i]);
        printf("%.3f", A[n][n+1]/A[n][n]);
    }
    
    int main()
    {
        scanf("%d", &n);
        double a[N], d[N];
        for (int i = 1; i <= n; i++) scanf("%lf", &a[i]);
        for (int i = 2; i <= n+1; i++) {
            double ans = 0;
            for (int j = 1; j <= n; j++)
                scanf("%lf", &d[j]), A[i-1][j] = 2*(d[j]-a[j]), ans += d[j]*d[j]-a[j]*a[j];
            A[i-1][n+1] = ans;
        }
        solve();
        return 0;
    }

    求解带环dp

    当dp方程并不满足最优子结构,即构成的决策图上有环时,如果决策会收敛(比如概率的指数衰减可以收敛),那么可以通过列方程求解。

    HNOI2013 游走

    一个无向连通图,顶点从1编号到N,边从1编号到M。
    小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。
    现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。
    N500

    思路与解: 我们考虑用一个转移方程描述这个问题。设f(i)为从1到i的期望走过每一条边次数构成的向量(l(E1),l(E2),,l(Em)),则方程为:

    f(1)=(0,0,0,,0)f(i)=ji(,(ji)+1dj,)

    解出该方程f(n)之后贪心的编号即可。然而我们的矩阵方程是n×n2的,可以被卡到O(n4)。一个解决方法是将f写在矩阵一边,l写在另一边,如果用快速矩阵乘法的奥义可以优化到O(n3+T(n×n,n×n2)),应该可以通过此题!

    然而我们显然不能随手写出O(nω)的矩阵乘法…因此考虑更换思路,设h(i)为期望经过节点i的次数,则可以列出以下方程:

    h(1)=1h(n)=0h(i)=ji1djh(j)

    h(n)=0是为了防止对其他节点产生影响。求解完之后每条边ij的期望经过次数就是1dih(i)+1djh(j),复杂度成功的降到了O(n3)

    返回来看我们一开始的思路,其有大量优化余地的原因是右侧矩阵虽然是O(n×n2)的,但最多只有O(n2)个元素。因此我们可以不再维护和这个点对无关的边的信息,转而维护点的性质,从而解决了这个问题。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 505;
    int g[N][N], n, m;
    double A[N][N];
    const double eps = 1e-9;
    
    void solve(double ans[])
    {
        for (int i = 1; i < n; i++) {
            int pos = i;
            for (int k = i+1; k <= n; k++)
                if (abs(A[k][i]) > abs(A[pos][i]))
                    pos = k;
            if (pos != i)
                swap(A[i], A[pos]);
            for (int j = i+1; j <= n; j++) {
                for (int k = i+1; k <= n+1; k++)
                    A[j][k] -= A[i][k]*(A[j][i]/A[i][i]);
                A[j][i] = 0;
            }
        }
        for (int i = n; i >= 1; i--) {
            for (int j = 1; j < i; j++)
                A[j][n+1] -= A[i][n+1]*(A[j][i]/A[i][i]), A[j][i] = 0;
            ans[i] = A[i][n+1]/A[i][i];
        }
    }
    
    double ans[N];
    double E[N*N];
    int top = 0;
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int u, v; scanf("%d%d", &u, &v);
            g[u][v] = g[v][u] = 1;
            g[u][u]++, g[v][v]++;
        }
        for (int i = 1; i < n; i++) {
            A[i][i] = -1;
            for (int j = 1; j <= n; j++)
                if (g[i][j] && i != j)
                    A[i][j] = 1.0/g[j][j];
        }
        A[n][n] = 1, A[n][n+1] = 0, A[1][n+1] = -1;
        solve(ans);
        ans[n] = 1;
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < i; j++)
                if (g[i][j])
                    E[++top] = ans[i]*1.0/g[i][i]+ans[j]*1.0/g[j][j];
        }
        for (int i = 1; i < n; i++)
            if (g[i][n])
                E[++top] = ans[i]*1.0/g[i][i];
        sort(E+1, E+m+1);
        double a = 0;
        for (int i = 1; i <= m; i++)
            a += (m-i+1)*E[i];
        printf("%.3f", a);
        return 0;
    }

    Usaco2010 Hot Dotp驱赶猪猡

    给定一个无向图,炸弹从1号点出发,每个时刻有P/Q的概率爆炸,如果没有爆炸,就会等概率沿着随机一条出边走到下一个城市,求最终每个城市的爆炸概率。
    n300

    思路与解: 我们设f(i)为i时刻炸弹停留再每个城市概率形成的向量,容易构造一个n×n的转移矩阵A,使得

    Af(i)=f(i+1)

    我们考虑最终答案ans,有

    ans=PQi=0Aif(1)

    由于我们知道答案随i指数收敛,因此必然有

    i=0Ai=IIA

    也就是无限递减几何级数。

    然后只要求解矩阵方程:

    (IA)×ans=PQf(1)

    嘴巴AC而已

    行列式与Matrix_Tree定理

    行列式是个奇怪的东西。它可以用许多方式定义,也有着丰富的应用,并且从每个应用都能引出对其的一种定义。矩阵树定理告诉我们:一个图的生成树个数等于其度数矩阵减去邻接矩阵,关于任一一个元素的余子式的绝对值。

    bzoj4031 小Z的房间

    Description

    你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
    你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。

    Input

    第一行两个数分别表示n和m。
    接下来n行,每行m个字符,每个字符都会是.或者*,其中.代表房间,*代表柱子。

    Output

    一行一个整数,表示合法的方案数 Mod 10^9

    思路与解: 裸题。随手A??

    并不..注意到模数并不是素数,不能简单的用逆元表示除法。
    我们需要一个被称为“模意义下行列式”的神奇方法。具体来说就是利用辗转相除的思路消元。辗转相除的过程中我们可以把a,bb,a mod b最终将b消成0,这里只需要模拟这个过程就可以了。可以参考2

    注意在消元的过程中只能严格的用两种初等变换,即

    • 交换两行
    • 将一行乘以常数,加到另一行
    // heoi2015 小z的房间
    #include <bits/stdc++.h>
    using namespace std;
    
    int p[10][10], num[15][15], d[一个101], top = 0;
    long long g[101][101];
    int n, m;
    char str[10];
    int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
    long long mod = 1e9;
    
    long long Gauss(long long M[101][101], int n)
    {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                ((M[i][j] %= mod) += mod ) %= mod;
        int dsgn = 1;
        for (int i = 1; i <= n; i++) {
            if (M[i][i] == 0) {
                int k = i+1;
                while (k <= n && M[k][i] == 0) k++;
                if (k > n) return 0; // 无解
                swap(M[i], M[k]);
                dsgn *= -1;
            }
            for (int j = i+1; j <= n; j++) {
                if (M[j][i] > M[i][i])
                    swap(M[i], M[j]), dsgn *= -1;
                long long A = M[i][i], B = M[j][i];
                while (B) {
                    long long t = A/B; A %= B, swap(A, B);
                    for (int k = 1; k <= n; k++)
                        ((M[i][k] -= t*M[j][k]) += mod) %= mod;
                    swap(M[i], M[j]);
                    dsgn *= -1;
                }
            }
        }
        long long ans = dsgn;
        for (int i = 1; i <= n; i++)
            (ans *= M[i][i]) %= mod;
        return (ans += mod) %= mod;
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            scanf("%s", str+1);
            for (int j = 1; j <= m; j++)
                if (str[j] == '.')
                    p[i][j] = 1, num[i][j] = ++top;
                else
                    p[i][j] = 0;
        }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                for (int k = 0; k < 4; k++)
                    if (num[i+dx[k]][j+dy[k]]) {
                        g[num[i][j]][num[i+dx[k]][j+dy[k]]] = -1;
                        g[num[i][j]][num[i][j]]++;
                    }
        long long ans = Gauss(g, top-1);
        cout << ans << endl;
        return 0;
    }

    逆矩阵

    我们知道非奇异n阶矩阵和矩阵乘法构成一群,也就是说每个元素都有逆元。考虑如何求解逆矩阵。使用Gauss-Jordan方法,即对矩阵[A|I]运用消元,将A变成单位矩阵,则右侧矩阵就是其逆矩阵。

    bzoj4128 Matrix

    对于非奇异矩阵A,B和一质数p,求满足

    AxB(mod p)

    的最小的x。

    思路与解:一眼的BSGS。就是设s=p,我们对于i[0,s)暴力计算并丢进哈希表,对于xs,它必然可以唯一确定的写成ls+r的形式且r<s(也就是带余除法)。我们只要枚举l,计算Als并查表即可。

    然而这题卡常数…我每次循环使用三次乘法果断gg…使用有理有据的底层优化才能AC..

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 75;
    int n, mod;
    int inv[20005];
    int power(int a, int n)
    {
        if (n == 0) return 1;
        int p = power(a, n>>1);
        p = p*p%mod;
        if (n&1) p = p*a%mod;
        return p;
    }
    
    void read(int A[MAXN][MAXN])
    {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                scanf("%d", &A[i][j]);
    }
    
    void put(int A[MAXN][MAXN])
    {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++)
                printf("%d ", A[i][j]);
            puts("");
        }
    }
    
    int M[MAXN][MAXN], A[MAXN][MAXN], B[MAXN][MAXN], ret[MAXN][MAXN];
    void inverse(int A[MAXN][MAXN], int ans[MAXN][MAXN])
    {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                M[i][j] = A[i][j], ans[i][j] = 0;
        for (int i = 1; i <= n; i++) ans[i][i] = 1;
        for (int i = 1; i <= n; i++) {
            int pos = i;
            for (register int k = i; k <= n; k++)
                if (M[k][i] != 0) {
                    pos = k; break;
                }
            swap(M[i], M[pos]);
            int iv = inv[M[i][i]];
            for (int j = 1; j <= n; j++)
                M[i][j] = M[i][j]*iv%mod, ans[i][j] = ans[i][j]*iv%mod;
            for (register int j = 1; j <= n; j++) {
                if (j == i) continue;
                int tab = M[j][i];
                for (register int k = 1; k <= n; k++) {
                    M[j][k] = ((M[j][k]-M[i][k]*tab)%mod+mod)%mod;
                    ans[j][k] = ((ans[j][k]-ans[i][k]*tab)%mod+mod)%mod;
                }
            }
        }
    }
    
    void mul(int A[MAXN][MAXN], int B[MAXN][MAXN], int C[MAXN][MAXN])
    {
        memset(ret, 0, sizeof ret);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++) {
                int mid = n/4, t = mid*4;
                for (register int k = 1; k < t; k += 4) {
                    ret[i][j] = (ret[i][j] + A[i][k]*B[k][j])%mod;
                    ret[i][j] = (ret[i][j] + A[i][k+1]*B[k+1][j])%mod;
                    ret[i][j] = (ret[i][j] + A[i][k+2]*B[k+2][j])%mod;
                    ret[i][j] = (ret[i][j] + A[i][k+3]*B[k+3][j])%mod;
                }
                ret[i][j] = (ret[i][j] + A[i][t+1]*B[t+1][j])%mod;
                ret[i][j] = (ret[i][j] + A[i][t+2]*B[t+2][j])%mod;
                ret[i][j] = (ret[i][j] + A[i][t+3]*B[t+3][j])%mod;
            }
        memcpy(C, ret, sizeof ret);
    }
    
    unsigned int hash_val(int A[MAXN][MAXN])
    {
        unsigned int val = 0;
        for (int i = 1; i <= n; i++)
            val = (val+A[i][i])*mod;
        return val;
    }
    map<int,int> mp;
    
    int I[MAXN][MAXN], C[MAXN][MAXN], D[MAXN][MAXN], T[MAXN][MAXN];
    
    int main()
    {
        scanf("%d%d", &n, &mod);
        for (register int i = 1; i < mod; i++)
            inv[i] = power(i, mod-2);
        for (int i = 1; i <= n; i++)
            I[i][i] = 1;
        read(A);
        read(B);
        int stp = sqrt(mod)+1;
        memcpy(C, I, sizeof I);
        for (int i = 0; i < stp; i++) {
            mp[hash_val(C)] = i;
            mul(C, A, C);
        }
        inverse(C, D);
        memcpy(T, I, sizeof I);
        for (int i = 0; i <= stp; i++) {
            int ht = hash_val(B);
            if (mp[ht]) {
                cout << i*stp+mp[ht] << endl;
                return 0;
            }
            mul(B, D, B);
        }
        return 0;
    }

    bzoj3168钙铁锌硒维生素

    留一个坑。大概思路就是求是否存在一组非零向量使其通过A线性组合出一组向量。放在矩阵里一起处理即可。

    线性基

    就是对一个自然数集合S找一个最小的的集合R,使得xS,c,Rc1Rc2Rc|c|=x

    由于异或可以看成模2意义下的加法,线性基其实是S对应向量空间的极大线性无关组。由于矩阵行秩等于列秩,线性基中元素只有O(lgn)

    如何构造一个线性基?只要在线的加入即可。注意逐位考虑。O(lgRmax)

    bool add(long long num)
    {
        for (int i = 1; i <= tp; i++) {
            if ((num^k[i]) < num && (num^k[i]) < k[i])
                num ^= k[i];
        }
        if (num != 0) {
            k[++tp] = num;
            for (int i = tp; i-1 && k[i-1] < num; i--)
                swap(k[i-1], k[i]);
            return 1;
        }
        return 0;
    }

    2460: [BeiJing2011]元素

    线性基裸题,只要贪心加入即可。
    证明?由于不会有不相交的线性无关组,考虑两个序列,容易说明贪心策略不劣于任何策略。

    #include <bits/stdc++.h>
    using namespace std;
    
    long long k[70], n, tp = 0;
    struct p {
        long long num, mag;
    } ps[1005];
    bool cmp(const p &a, const p &b)
    { return a.mag > b.mag; }
    bool add(long long num)
    {
        for (int i = 1; i <= tp; i++) {
            if ((num^k[i]) < num && (num^k[i]) < k[i])
                num ^= k[i];
        }
        if (num != 0) {
            k[++tp] = num;
            for (int i = tp; i-1 && k[i-1] < num; i--)
                swap(k[i-1], k[i]);
            return 1;
        }
        return 0;
    }
    
    int main()
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%lld%d", &ps[i].num, &ps[i].mag);
        sort(ps+1, ps+n+1, cmp);
        int ans = 0;
        for (int i = 1; i <= n; i++)
            if (add(ps[i].num))
                ans += ps[i].mag;
        cout << ans << endl;
        return 0;
    }

    [Scoi2016]幸运数字

    问树上路径走过的元素形成的集合的最大异或值。

    思路与解: 由于所有元素的异或值和线性基的异或值等价,且线性基合并是满足结合律的,我们只要用树上倍增维护线性基合并即可。注意判断a=b

    #include <bits/stdc++.h>
    using namespace std;
    
    struct linear_basis {
        long long k[64];
        int top;
        inline void clear() { top = 0; }
        linear_basis() { clear(); }
        void push(long long num)
        {
            for (int i = 1; i <= top; i++)
                if ((k[i]^num) < k[i] && (k[i]^num) < num)
                    num ^= k[i];
            if (num) {
                k[++top] = num;
                for (int i = top; i-1 && k[i-1] < k[i]; i--)
                    swap(k[i-1], k[i]);
            }
        }
        friend linear_basis operator + (linear_basis a, linear_basis b)
        {
            for (int i = 1; i <= a.top; i++)
                b.push(a.k[i]);
            return b;
        }
        long long max()
        {
            long long ans = k[1];
            for (int i = 2; i <= top; i++)
                if ((ans^k[i]) >= ans)
                    ans ^= k[i];
            return ans;
        }
        void print()
        {
            for (int i = 1; i <= top; i++)
                cout << k[i] << " ";
            cout << endl;
        }
    } ;
    
    const int MAXN = 20005;
    linear_basis f[MAXN][16];
    int fa[MAXN][16], depth[MAXN];
    long long a[MAXN];
    int n, q;
    struct node {
        int to, next;
    } edge[MAXN*2];
    int head[MAXN], top, u, v;
    inline void push(int i, int j)
    { ++top, edge[top] = {j, head[i]}, head[i] = top; }
    void dfs(int nd, int ft = 0)
    {
        fa[nd][0] = ft, f[nd][0].push(a[ft]), depth[nd] = depth[ft]+1;
        for (int i = head[nd]; i; i = edge[i].next)
            if (edge[i].to != ft)
                dfs(edge[i].to, nd);
    }
    
    linear_basis lca(int a, int b)
    {
        if (depth[a] < depth[b]) swap(a, b);
        linear_basis lb;
        for (int i = 0; i < 16; i++)
            if ((depth[a]-depth[b])&(1<<i))
                lb = lb + f[a][i], a = fa[a][i];
        if (a == b) return lb;
        for (int i = 15; i >= 0; i--)
            if (fa[a][i] != fa[b][i]) {
                lb = lb + f[a][i] + f[b][i];
                a = fa[a][i], b = fa[b][i];
            }
        if (a != b) lb = lb + f[a][0] + f[b][0];
        return lb;
    }
    
    int main()
    {
        scanf("%d%d", &n, &q);
        for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), f[i][0].push(a[i]);
        for (int i = 1; i < n; i++) {
            scanf("%d%d", &u, &v);
            push(u, v), push(v, u);
        }
        dfs(1);
        for (int j = 1; j < 16; j++)
            for (int i = 1; i <= n; i++)
                fa[i][j] = fa[fa[i][j-1]][j-1];
        for (int j = 1; j < 16; j++)
            for (int i = 1; i <= n; i++)
                f[i][j] = f[i][j-1]+f[fa[i][j-1]][j-1];
        for (int i = 1; i <= q; i++) {
            scanf("%d%d", &u, &v);
            if (u == v) printf("%lld
    ", a[u]);
            else printf("%lld
    ", lca(u, v).max());
        }
        return 0;
    }

    线性齐次递推

    我们考虑一个递推式f(n)=mi=1aif(ni),我们希望在更快的速度内完成计算。考虑用矩阵,我们发现:

    A=a111000a120100a130010a140000a1m10001a1m0000

    h(n)=f(n)f(n1)f(n2)f(nm)

    则有

    Ah(n)=h(n+1)

    由于矩阵乘法满足结合律,可以用快速幂计算。

    [HNOI2008]GT考试

    阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0Xi9),他不希望准考证号上出现不吉利的数字。
    他的不吉利数学A1A2...Am(0Ai9)有M位,不出现是指X1X2...Xn中没有恰好一段等于A1A2...Am. A1和X1可以为0。

    思路与解: 一个数位dp的套路,dp[i][j]表示还剩i位,匹配到第j位的方案数,则dp[i][j]=jidp[i1][k]。转移可以O(n)用kmp搞出来。然后就是矩阵乘起来了。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 30;
    int n, m, p;
    char str[30];
    int pi[30];
    int trans[30][10];
    int M[MAXN][MAXN];
    
    void kmp_init()
    {
        pi[0] = pi[1] = 0;
        int p = 0;
        for (int i = 2; i <= m; i++) {
            while (p && str[p+1] != str[i]) p = pi[p];
            if (str[p+1] == str[i]) p++;
            pi[i] = p;
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < 10; j++) {
                int k = i;
                while (k && str[k+1] != j+'0') k = pi[k];
                if (str[k+1] == j+'0')
                    trans[i][j] = k+1, M[k+1][i]++;
                else
                    M[0][i]++;
            }
        }
        M[m][m] = 10;
    }
    
    int R[MAXN][MAXN];
    
    void mul(int A[MAXN][MAXN], int B[MAXN][MAXN]) // A *= B
    {
        memset(R, 0, sizeof R);
        for (int i = 0; i <= m; i++)
            for (int j = 0; j <= m; j++)
                for (int k = 0; k <= m; k++)
                    (R[i][j] += A[i][k]*B[k][j]) %= p;
        for (int i = 0; i <= m; i++)
            for (int j = 0; j <= m; j++)
                A[i][j] = R[i][j];
    }
    
    int B[33][MAXN][MAXN], top = 0;
    
    void quick_power(int A[MAXN][MAXN], int n)
    {
        if (n == 1) return;
        ++top;
        for (int i = 0; i <= m; i++)
            for (int j = 0; j <= m; j++)
                B[top][i][j] = A[i][j];
        quick_power(B[top], n>>1);
        mul(B[top], B[top]);
        if (n&1) mul(B[top], A);
        for (int i = 0; i <= m; i++)
            for (int j = 0; j <= m; j++)
                A[i][j] = B[top][i][j];
    }
    
    int main()
    {
        scanf("%d%d%d%s", &n, &m, &p, str+1);
        kmp_init();
        quick_power(M, n);
        int ans = 0;
        for (int i = 0; i < m; i++)
            (ans += M[i][0]) %= p;
        cout << ans << endl;
        return 0;
    }

    [HNOI2011]数学作业

    给定n和m,求:

    1234567891011...n mod m

    思路与解: 可以不同位数分开考虑。考虑第k位上的情况:

    f(i)=10if(i1)+i

    由于要记录i的信息,我们要扩充状态表示:

    h(n)=f(i)f(i1)i+11

    可以构造矩阵:

    A=10k100000010100011

    即可。注意取模的技巧..否则会RE+WA+MLE.

    #include <bits/stdc++.h>
    using namespace std;
    
    long long n, m;
    long long D[5][5];
    
    void mul(long long A[5][5], long long B[5][5], long long C[5][5])
    {
        memset(D, 0, sizeof D);
        for (int i = 1; i <= 4; i++)
            for (int j = 1; j <= 4; j++)
                for (int k = 1; k <= 4; k++)
                    (D[i][j] += A[i][k]*B[k][j]%m) %= m;
        for (int i = 1; i <= 4; i++)
            for (int j = 1; j <= 4; j++) {
                C[i][j] = D[i][j];
                if (C[i][j] < 0) throw;
            }
    }
    
    long long B[5][5];
    
    void matrix_power(long long A[5][5], long long n)
    {
        memset(B, 0, sizeof B);
        for (int i = 1; i <= 5; i++) B[i][i] = 1;
        while (n) {
            if (n&1) mul(B, A, B);
            mul(A, A, A); n >>= 1;
        }
        for (int i = 1; i <= 5; i++)
            for (int j = 1; j <= 5; j++)
                A[i][j] = B[i][j];
    }
    
    long long power(long long a, long long n, long long mod = 3e18)
    {
        long long t = 1;
        while (n) {
            if (n&1) t = t*a%mod;
            a = a*a%mod;
            n >>= 1;
        }
        return t;
    }
    
    long long M[5][5];
    
    int main()
    {
        scanf("%lld%lld", &n, &m);
        long long ret = 0;
        for (int k = 1; k <= 19; k++) {
            if (power(10, k-1) > n) break;
            long long beg = power(10, k-1), tar = min(power(10, k)-1, n);
            memset(M, 0, sizeof M);
            M[1][1] = power(10, k, m), M[1][3] = 1;
            M[2][1] = M[3][3] = M[3][4] = M[4][4] = 1;
            matrix_power(M, tar-beg+1);
            ret = ret*power(power(10, (tar-beg+1), m)%m, k, m)%m;
            ret = (ret+M[1][3]*(beg%m)%m+M[1][4])%m;
        }
        cout << ret << endl;
        return 0;
    }
    
    // bug记录,函数默认参数忘记下传gg...
    // 尽量不用默认参数...

    参考资料3


    1. 《代数学引论I(BA I)》,前苏联-柯斯特利金著,张英伯译,高等教育出版社
    2. http://blog.csdn.net/u013010295/article/details/47451451
    3. 某集训,妹主席ppt
  • 相关阅读:
    springboot整合solr8.0
    Java学习笔记之JFileChooser的简单使用
    bootstrap-fileinput插件,上传成功后事件
    centos7中安装mysql8.0
    linux下安装mysql
    IDEA集成JRebel热部署和远程调试
    Flume学习之路 (一)Flume的基础介绍
    android studio的安装和卸载
    彻底卸载mysql
    电脑重装系统后各种环境和工具的安装配置
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684334.html
Copyright © 2011-2022 走看看