矩阵快速幂【集合】

15 篇文章 0 订阅

问题描述

克拉克是一名人格分裂患者。某一天,克拉克变成了一个研究人员,在研究数字。  
他想知道在所有长度在[l, r][l,r]之间的能被77整除且相邻数位之和不为kk的正整数有多少个。  [l, r][l,r]之间的能被77整除且相邻数位之和不为kk的正整数有多少个。  
输入描述
第一行一个整数T(1 \le T \le 5)T(1≤T≤5),表示数据的组数。  
每组数据只有一行三个整数l, r, k(1 \le l \le r \le 10^9, 0 \le k \le 18)l,r,k(1≤lr≤10​9​​,0≤k≤18)。  T(1 \le T \le 5)T(1≤T≤5),表示数据的组数。  
每组数据只有一行三个整数l, r, k(1 \le l \le r \le 10^9, 0 \le k \le 18)l,r,k(1≤lr≤10​9​​,0≤k≤18)。  
输出描述
每组数据输出一行一个数,表示答案。由于答案太大,你只需对10^9+710​9​​+7取模即可。  10^9+710​9​​+7取模即可。  
输入样例
2
1 2 5
2 3 5
输出样例
13
125
Hint
第一个样例有13个数满足,分别是:7,21,28,35,42,49,56,63,70,77,84,91,987,21,28,35,42,49,56,63,70,77,84,91,987,21,28,35,42,49,56,63,70,77,84,91,987,21,28,35,42,49,56,63,70,77,84,91,98

 

定义dp[i][j][k]表示位数为i,当前数%7的值而且以k结尾的方案数。

先列出dp方程,dp[i][x][ (t*10+x)%7 ]+=dp[i-1][j][t],因为i太大,所以要用矩阵快速幂加速。

 

#include <bits/stdc++.h>  
#define ll long long   
#define mod 1000000007  
using namespace std;  
typedef vector<long long> vec;  
typedef vector<vec> mat;  
mat mul(mat &A,mat &B)  
{  
    mat C(A.size(),vec(B[0].size()));  
    for(int i=0;i<A.size();i++)  
    {  
        for(int k=0;k<B.size();k++)  
        {  
            for(int j=0;j<B[0].size();j++)  
            {  
                C[i][j]=(C[i][j]+(A[i][k]*B[k][j])%mod)%mod;  
            }  
        }  
    }  
    return C;  
}  
mat pow(mat A,long long n)  
{  
    mat B(A.size(),vec(A.size()));  
    for(int i=0;i<A.size();++i)  
        B[i][i]=1;  
    while(n>0)  
    {  
        if(n&1)  
            B=mul(B,A);  
        A=mul(A,A);  
        n>>=1;  
    }  
    return B;  
}  
int main()  
{  
    int t;  
    cin>>t;  
    while(t--)  
    {  
        int l,r,m;  
        cin>>l>>r>>m;  
        mat A(71+2,vec(71+2));  
        mat AA(71, vec(1));  
        mat BB(71, vec(1));  
        for(int i=0;i<=9;++i){   //上一次的末位   
            for(int j=0;j<=9;++j){  //这一次的末位   
                if(i+j==m)  
                    continue;   
                for(int k=0;k<7;++k){  //上一次的余7后的余数   
                    A[(k*10+j)%7*10+j][k*10+i]++; //当前状态和前一个状态,将余数和末位合并起来存储   
                }  
            }  
        }  
        for(int i=0;i<=9;++i)  //求前缀和必加,本题较于下题加这个的原因在于长度可以是1~l/1~r,下题只能是固定长度
            A[70][i]=1;        //求前缀和必加,后面这个i(0~9)是指最终状态可能性(余数0,末位0~9) 
        A[70][70]=1;           //求前缀和必加   
          
        AA=pow(A,l-1);   
        BB=pow(A,r);   
        
        ll s1=0,s2=0;
        for(int i=1;i<=9;++i){
        	s1+=AA[70][(i%7)*10+i]; //初始状态不是0而是特殊处理,是因为首位不能为0 
        	s2+=BB[70][(i%7)*10+i]; 
        }
        cout<<(s2-s1+mod)%mod<<endl;  
    }  
    return 0;  
}  

 

 

[Topcoder]给出n,k,计算 1k + 2k + 3k + ... + nk modulo 1000000007.

 

要实现求和长度在k左右的递推式(n+1)^k-n^k=sigma(c[k][i]*n^i);

 

#include<bits/stdc++.h>   
#define ll long long  
#define MOD 1000000007  
using namespace std;  
ll CC[55][55];  
void permut(){  
    CC[0][0]=1;  
    for(int i=1;i<=50;++i){  
        CC[i][0]=1;  
        CC[i][i]=1;  
    }  
    for(int i=2;i<=50;++i){  
        for(int j=1;j<i;++j){  
            CC[i][j]=(CC[i-1][j]+CC[i-1][j-1])%MOD;  
        }  
    }  
}  
typedef vector<ll> vec;  
typedef vector<vec> mat;  
mat mul(mat &A,mat &B){  
    mat C(A.size(),vec(B[0].size()));  
    for(int i=0;i<A.size();i++){  
        for(int k=0;k<B.size();k++){  
            for(int j=0;j<B[0].size();j++){  
                C[i][j]=(C[i][j]+(A[i][k]*B[k][j])%MOD)%MOD;  
            }  
        }  
    }  
    return C;  
}  
mat pow(mat A,ll n){  
    mat B(A.size(),vec(A.size()));  
    for(int i=0;i<A.size();++i)  
        B[i][i]=1;  
    while(n>0){  
        if(n&1)  
            B=mul(B,A);  
        A=mul(A,A);  
        n>>=1;  
    }  
    return B;  
}  
int main()  
{  
    int t;  
    cin>>t;  
    permut();  
    while(t--){  
        int n,k;  
        cin>>n>>k;  
        mat A(k+2,vec(k+2));  
        mat B(k+2, vec(1));  
        for(int i=0;i<=k;++i){  
            for(int j=0;j<=i;++j){  
                A[i][j]=CC[i][j]; //i次方项的系数是由Σ((0<=j<=i)次方项乘上C(i,j))得出   
            }  
        }  
        //求前缀和必加   
        for(int i=0;i<=k;++i)  
            A[k+1][i]=CC[k][i];  //这一步不理解(为什么加的是CC[k][i])   
        A[k+1][k+1]=1;  
          
        B[0][0]=1;   
        A=pow(A,n);  
        B=mul(A,B);  
        cout<<B[k+1][0]<<endl;  
    }  
    return 0;  
}

 

 

 

 

 

b个堆,每个堆有n个数字,每一个堆取一个数字,第i个堆取一个数字做第i位的数字,构成一个n位数,求构成的数字被x除余k有多少种方案。

http://codeforces.com/contest/621/problem/E

 

#include <bits/stdc++.h>  
#define ll long long   
#define mod 1000000007  
using namespace std;  
typedef vector<long long> vec;  
typedef vector<vec> mat;  
mat mul(mat &A,mat &B)  
{  
    mat C(A.size(),vec(B[0].size()));  
    for(int i=0;i<A.size();i++)  
    {  
        for(int k=0;k<B.size();k++)  
        {  
            for(int j=0;j<B[0].size();j++)  
            {  
                C[i][j]=(C[i][j]+(A[i][k]*B[k][j])%mod)%mod;  
            }  
        }  
    }  
    return C;  
}  
mat pow(mat A,long long n)  
{  
    mat B(A.size(),vec(A.size()));  
    for(int i=0;i<A.size();++i)  
        B[i][i]=1;  
    while(n>0)  
    {  
        if(n&1)  
            B=mul(B,A);  
        A=mul(A,A);  
        n>>=1;  
    }  
    return B;  
}  
int num[11];
int main(){  
	memset(num,0,sizeof(num));
    int n,b,k,x,a;
    cin>>n>>b>>k>>x;
    for(int i=1;i<=n;++i){
    	cin>>a;
    	num[a]++;
    }
    mat A(x+2,vec(x+2));  
    mat B(x+2, vec(1));  
    for(int i=0;i<x;++i){   //上一次%x后的余数 
        for(int j=1;j<=9;++j){  //这一次的末位   
            A[(i*10+j)%x][i]+=num[j]; //j这个数字在数组中出现的次数  
        }  
    }  
//        for(int i=0;i<=9;++i)  //求前缀和必加   
//        A[x][i]=num[i];        //求前缀和必加   
//        A[x][x]=1;           //求前缀和必加   
    A=pow(A,b);  
    cout<<A[k][0]<<endl;  
    return 0;  
}  

【快速幂变形】CD484C 一般做法会超时,但很难想到快速幂

 

//思路:首先 他是对1到k 元素做一次变换,然后对2到k+1个元素做一次变化。。。。依次做完。  
//     如果我们对1到k个元素做完一次变换后,把整个数组循环左移一个。  
//   那么第二次还是对1 到 k个元素做和第一次一样的变换,再左移,  
//   再对1 到 k个元素做和第一次一样的变换,依次做完n-k+1即可。  
//   假设题目要求的变换为C    循环左移变换为P。那么对于每次查询 相当于做 n-k+1 CP)变换。  
//   最后把答案再向右移动k-1  回到原来位置即可。  
//   那么问题就解决了   效率    每次查询n log(n-k+1)     
#include<bits/stdc++.h>  
using namespace std; 
char s[1000005]; 
char a[1000005]; 
int p[1000005]; 
int ans[1000005]; 
int tmp[1000005]; 
int main(){  
    int n,m; 
    scanf("%s",s); 
    n=strlen(s); 
    scanf("%d",&m); 
    for(int i=0;i<m;++i){  
        int k,d; 
        scanf("%d%d",&k,&d); 
        for(int j=0;j<n;++j)  
            p[j]=ans[j]=j; 
        int c=0; 
        for(int x=0;x<d;++x)  
            for(int j=x;j<k;j+=d)  {
                p[c++]=j; //这个下标是由第几个数存放   
//              p[j]=x*d+j/d;  //第j个数最后被保存的下标(每次取前K个下标操作,这样不好取)   
            }  
        int last=p[0]; 
        for(int j=0;j <n-1;++j)  
            p[j]=p[j+1]; //对1到k个元素做完一次变换后,把整个数组循环左移一个   
        p[n-1]=last; 
        int x=n-k +1; 
        while(x){  
            if(x&1){  
                for(int j=0;j<n;++j)  
                    tmp[j]=ans[p[j]]; //这个下标是由第几个数存放   
                for(int j=0;j<n;++j)  
                    ans[j]=tmp[j];//021345,类似p[p[]]的操作,区别是在p变了几次后集中处理一次   
            }  
            for(int j=0;j<n;++j)  
                tmp[j]=p[p[j]]; 
            for(int j=0;j<n;++j)  
                p[j]=tmp[j]; 
            x >>= 1; 
        }  
        for(int j=0;j<n;++j)  {
            a[j]=s[ans[(j+k-1)%n]]; 
        }  
        printf("%s\n",a); 
        for(int j=0;j<n;++j)  {
            s[j]=a[j];  //传递字符串a到s   
        }       
    }  
    return 0; 
}  

 

【poj3613】从s到e恰好经过n条边的最短路(可以有重边)。

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
int N,T,S,E,num;
map<int,int> mp;
struct Matrix{
    int ma[210][210];
    void clear(){
        memset(ma,0x3f,sizeof(ma));//初始化一定要大,否则WA
    }
};
Matrix Floyd(Matrix a,Matrix b){
    Matrix dis;
    dis.clear();
    int i,j,k;
    for(k=1;k<=num;k++) //一次floyd  是找到一个中间点
        for(i=1;i<=num;i++)
            for(j=1;j<=num;j++)
                if(dis.ma[i][j]>a.ma[i][k]+b.ma[k][j])
                    dis.ma[i][j]=a.ma[i][k]+b.ma[k][j];
    return dis;
}
Matrix Solve(Matrix a,int k){
    Matrix ans=a;
    while(k){
        if(k&1){
            ans=Floyd(ans,a);
        }
        a=Floyd(a,a);
        k>>=1;
    }
    return ans;
}
int main(){
    Matrix a;
    while(~scanf("%d%d%d%d",&N,&T,&S,&E)){
        num=0;
        mp.clear();
        a.clear();
        int u,v,w;
        while(T--){
            scanf("%d%d%d",&w,&u,&v);
            if(mp[u]==0)
                mp[u]=++num;
            if(mp[v]==0)
                mp[v]=++num;
            if(a.ma[mp[u]][mp[v]]>w)
                a.ma[mp[u]][mp[v]]=a.ma[mp[v]][mp[u]]=w;
        }   
        a=Solve(a,N-1);    // N 条边  ,经过 N-1 个点 
        printf("%d\n",a.ma[mp[S]][mp[E]]);
    }
    return 0;
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值