什么是矩阵
- 对于 矩阵的的初步认识这里有一些讲解。
- 一开始我对矩阵奇葩的运算方式感到奇怪,它有什么用。。。
- DP 问题的加速可以利用矩阵的结合律来实现。
- 同时一些图论问题,利用矩阵有关图的联通和边权问题我们又有了一个新招了。(讲道理,要用到矩阵的题目似乎都是为它专门设计的。)
- 同时还有其他的作用可以参考大牛的博客十个利用矩阵乘法解决的经典题目, code
矩阵乘法在图联通中的意义
⎡⎣⎢⎢⎢0011100000010100⎤⎦⎥⎥⎥∗⎡⎣⎢⎢⎢0011100000010100⎤⎦⎥⎥⎥−>⎡⎣⎢⎢⎢0101001101001000⎤⎦⎥⎥⎥
- 我们可以容易的发现这就是令一个点沿着边走两步得到的边。
例题
XB的生日
- 题目有三个限制条件
- 走 [1,T] 步回到原点。
- 访问商店有额外的 cost
- 买齐4个物品。
- 我们来逐一解决他们
first
- 我们需要算出
A1+A2+A3+A4+A5+A6……An
中所有的
A[1][1]
和。我们可以通过构造矩阵来实现。
[cnt1cnt2…cntnans]∗⎡⎣⎢⎢⎢⎢⎢⎢001:0100:0000:…………:1⎤⎦⎥⎥⎥⎥⎥⎥−>[cnt1cnt2…cntnans] - 用
cnti
表示从一出发可以达到的
i
的方案,我们再在原矩阵的边缘右边赋值成和
A[i][1] 一样,边缘右边下面为 0 ,把右下角赋值为1 这样我们就做到了把 A1+A2+A3……An 中所有的 A[1][1] 和记录到了 ans 中。 - 解释一下:由于最后一列和
A[i][1]
是一样,故我们得到的点积即为
ans′=从当前从1回到1的方案数+ans
- 又由于矩阵满足结合律所以我们可以通过计算 B∗An 得到答案。
- 构造的思路,我们要求的是
A1+A2+A3……An
中所有的
A[1][1]
于是,我们需要把所有的
A[1][1]
都加起来,我们分析可以得到
A[1][1]′=∑i=1ncnti∗A[i][1],我们要求的是 ans′=A[1][1]′+ans 即ans′=∑i=1ncnti∗A[i][1]+ans
- 其中 cnti 和 A[i][1] 都是上一层的信息,这样就可以构造了。
then
- 由于有些边的边权为2,所以我们可以把点裂开。如果你要去
i
买东西就先去
i′ 再到 i 即可。
last
- 我们最后可以通过容斥原理限制买的集合得到答案。
-
1111−(1110+1011+0111+1101)+(0011+1010+1100+1001)−(1000+0100+0010+0001)+0000=ans
(数字为1表示可以去用这样物品的店)。
ans
为一定买到所有物品的答案。
#include<bits/stdc++.h>
#define ID(a,b) ((a-1)*2+b)
using namespace std;
const int M=505,P=5557;
int a,n,m,T,ans,x[M],y[M],s[M];
char str[10];
struct Mat{
int r,c,m[60][60];
void init(int a,int b,bool f=0){
r=a;c=b;
for(int i=1;i<=r;i++)for(int j=1;j<=c;j++)m[i][j]=(bool)f&&(i==j);
}void pt(){
for(int i=1;i<=r;i++){for(int j=1;j<=c;j++)printf("%d",m[i][j]);puts("");}puts("");
}
}A,B;
Mat operator*(const Mat &a,const Mat &b){
Mat res;res.init(a.r,b.c);
for(int k=1;k<=res.c;++k){
for(int i=1;i<=res.r;++i){
if(a.m[i][k]==0)continue;
for(int j=1;j<=res.c;++j){
if(b.m[k][j]==0)continue;
res.m[i][j]=(a.m[i][k]*b.m[k][j]+res.m[i][j])%P;
}
}
}return res;
}
Mat pow(Mat A,int n){
Mat ans,p=A;
ans.init(A.r,A.c,1);
while(n){
if(n&1)ans=ans*p,n--;
n>>=1,p=p*p;
}
return ans;
}
int calc(int S){
A.init(a,a);
B.init(1,a);
B.m[1][1]=1;
A.m[a][a]=1;
for(int i=1;i<=n;i++)A.m[ID(i,1)][ID(i,2)]=1;
for(int i=1;i<=m;i++){
A.m[ID(x[i],1)][ID(y[i],1)]=1;
if((s[i]|S)==S)A.m[ID(x[i],2)][ID(y[i],1)]=1;
if(y[i]==1){
A.m[ID(x[i],1)][a]=1;
if((s[i]|S)==S)A.m[ID(x[i],2)][a]=1;
}
}
B=B*pow(A,T);
return B.m[1][a];
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d %s",x+i,y+i,str);
for(int j=0;j<strlen(str);j++){
if(str[j]=='B')s[i]|=1;
if(str[j]=='J')s[i]|=2;
if(str[j]=='M')s[i]|=4;
if(str[j]=='P')s[i]|=8;
}
}scanf("%d",&T);
a=n*2+1;
for(int i=0;i<16;i++){
bool f=0;for(int j=0;j<4;j++)if(i&(1<<j))f^=1;
ans=(ans+(f?-1:1)*calc(i))%P;
}
printf("%d",(ans%P+P)%P);
return 0;
}
Gremlin的繁殖
- 通过分析我们发现该题就是让我们求在图上通过一定的
cost
可以通过最多的边。
- 又发现原图的边和点都已经固定了,于是我们可以利用矩阵来二分(倍增)。
include<bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
void Min(ll &A,ll B){if(A>B)A=B;}
const int M=105;
const ll INF=1e15;
int n,A[M][1005];
ll T,ans;
struct Mat{
ll m[M][M];
Mat(){For(i,0,104)For(j,0,104)m[i][j]=INF;}
bool operator <=(const ll &x)const{
ll mi=INF;
For(i,1,n)For(j,1,n)Min(mi,m[i][j]);
return mi<=x;
}
Mat operator +(const Mat &A)const{
Mat res;
For(k,1,n)For(i,1,n)For(j,1,n)
Min(res.m[i][j],m[i][k]+A.m[k][j]);
return res;
}
}S[55],O,t;
int main(){
scanf("%d %lld",&n,&T);
for(int i=1,x,c,l;i<=n;i++){
scanf("%d %d",&l,&c);
for(int j=1;j<=l;j++)scanf("%d",&A[i][j]);
for(int j=1;j<=l;j++){
scanf("%d",&x);
Min(S[0].m[i][A[i][j]],1ll*x+c);
}
}
for(int i=1;i<=50;i++)S[i]=S[i-1]+S[i-1];
for(int i=1;i<=n;i++)O.m[i][i]=0;
for(int i=50;i>=0;i--){
Mat t=O+S[i];
if(t<=T)ans+=1ll<<i,O=t;
}
printf("%lld\n",ans);
return 0;
}
1111−(1110+1011+0111+1101)+(0011+1010+1100+1001)−(1000+0100+0010+0001)+0000=ans
(数字为1表示可以去用这样物品的店)。
ans
为一定买到所有物品的答案。#include<bits/stdc++.h>
#define ID(a,b) ((a-1)*2+b)
using namespace std;
const int M=505,P=5557;
int a,n,m,T,ans,x[M],y[M],s[M];
char str[10];
struct Mat{
int r,c,m[60][60];
void init(int a,int b,bool f=0){
r=a;c=b;
for(int i=1;i<=r;i++)for(int j=1;j<=c;j++)m[i][j]=(bool)f&&(i==j);
}void pt(){
for(int i=1;i<=r;i++){for(int j=1;j<=c;j++)printf("%d",m[i][j]);puts("");}puts("");
}
}A,B;
Mat operator*(const Mat &a,const Mat &b){
Mat res;res.init(a.r,b.c);
for(int k=1;k<=res.c;++k){
for(int i=1;i<=res.r;++i){
if(a.m[i][k]==0)continue;
for(int j=1;j<=res.c;++j){
if(b.m[k][j]==0)continue;
res.m[i][j]=(a.m[i][k]*b.m[k][j]+res.m[i][j])%P;
}
}
}return res;
}
Mat pow(Mat A,int n){
Mat ans,p=A;
ans.init(A.r,A.c,1);
while(n){
if(n&1)ans=ans*p,n--;
n>>=1,p=p*p;
}
return ans;
}
int calc(int S){
A.init(a,a);
B.init(1,a);
B.m[1][1]=1;
A.m[a][a]=1;
for(int i=1;i<=n;i++)A.m[ID(i,1)][ID(i,2)]=1;
for(int i=1;i<=m;i++){
A.m[ID(x[i],1)][ID(y[i],1)]=1;
if((s[i]|S)==S)A.m[ID(x[i],2)][ID(y[i],1)]=1;
if(y[i]==1){
A.m[ID(x[i],1)][a]=1;
if((s[i]|S)==S)A.m[ID(x[i],2)][a]=1;
}
}
B=B*pow(A,T);
return B.m[1][a];
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d %s",x+i,y+i,str);
for(int j=0;j<strlen(str);j++){
if(str[j]=='B')s[i]|=1;
if(str[j]=='J')s[i]|=2;
if(str[j]=='M')s[i]|=4;
if(str[j]=='P')s[i]|=8;
}
}scanf("%d",&T);
a=n*2+1;
for(int i=0;i<16;i++){
bool f=0;for(int j=0;j<4;j++)if(i&(1<<j))f^=1;
ans=(ans+(f?-1:1)*calc(i))%P;
}
printf("%d",(ans%P+P)%P);
return 0;
}
include<bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
void Min(ll &A,ll B){if(A>B)A=B;}
const int M=105;
const ll INF=1e15;
int n,A[M][1005];
ll T,ans;
struct Mat{
ll m[M][M];
Mat(){For(i,0,104)For(j,0,104)m[i][j]=INF;}
bool operator <=(const ll &x)const{
ll mi=INF;
For(i,1,n)For(j,1,n)Min(mi,m[i][j]);
return mi<=x;
}
Mat operator +(const Mat &A)const{
Mat res;
For(k,1,n)For(i,1,n)For(j,1,n)
Min(res.m[i][j],m[i][k]+A.m[k][j]);
return res;
}
}S[55],O,t;
int main(){
scanf("%d %lld",&n,&T);
for(int i=1,x,c,l;i<=n;i++){
scanf("%d %d",&l,&c);
for(int j=1;j<=l;j++)scanf("%d",&A[i][j]);
for(int j=1;j<=l;j++){
scanf("%d",&x);
Min(S[0].m[i][A[i][j]],1ll*x+c);
}
}
for(int i=1;i<=50;i++)S[i]=S[i-1]+S[i-1];
for(int i=1;i<=n;i++)O.m[i][i]=0;
for(int i=50;i>=0;i--){
Mat t=O+S[i];
if(t<=T)ans+=1ll<<i,O=t;
}
printf("%lld\n",ans);
return 0;
}