您的位置:首页 > 其它

矩阵快速幂【集合】

2016-02-06 16:54 183 查看
其实我对原理不是很了解,对最后一行前缀和和B数组也不是很精,不过。。能做出来就好。。能凑对就好。。。

问题描述
克拉克是一名人格分裂患者。某一天,克拉克变成了一个研究人员,在研究数字。
他想知道在所有长度在[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≤l≤r≤10​9​​,0≤k≤18)。


输出描述
每组数据输出一行一个数,表示答案。由于答案太大,你只需对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,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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: