To_Heart—题解——SCOI2009迷路

目录

  • 题目
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例
      • 样例输入 1
      • 样例输出 1
      • 样例输入 2
      • 样例输出 2
    • 数据范围和提示:
  • 题解
    • 第一部分
      • 举个栗子
    • 第二部分
  • 代码

题目

题目描述

** 原题来自:SCOI 2009**

Windy 在有向图中迷路了. 该有向图有 n n n个节点,Windy 从节点 0 0 0 出发,他必须恰好在 T T T时刻到达节点 N − 1 N-1 N1

现在给出该有向图,你能告诉 Windy 总共有多少种不同的路径吗?

注意:Windy 不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。

输入格式

第一行包含两个整数 N N N, T T T

接下来有 N N N行,每行一个长度为 N N N的字符串。第 i i i行第 j j j列为 0表示从节点 i i i到节点 j j j没有边,为 1 1 1 9 9 9表示从节点 i i i节点 j j j需要耗费的时间。

输出格式

包含一个整数,可能的路径数,这个数可能很大,只需输出这个数除以 2009 2009 2009的余数。

样例

样例输入 1

2 2
11
00

样例输出 1

1

样例输入 2

5 30
12045
07105
47805
12024
12345

样例输出 2

852

数据范围和提示:

2 <= N <= 10 ; 1 <= T <= 1000000000 ;0<=边权<=9;

题解

第一反应:咦?这不是图论吗???
默默的看了眼T的范围,死了心

DP!! DP一定可以!!!

默默的看了眼T的范围,又死了心

那么怎么做呢?

第一部分

首先,我们把这道题想简单一点,如果题目中的每一条边都没有边权,只用1或0来表示两个点之间是否存在边,并且用邻接矩阵来存这张图,那么我们又可以得到些什么呢?

举个栗子

我们以下面这张图为例
To_Heart—题解——SCOI2009迷路_第1张图片
以邻接矩阵来表示这个矩阵:

0 1 1
1 0 1
1 0 0                 //矩阵1

其中,aij表示i到j之间是否有连线;

那么,我们把它平方一下,又可以得到什么呢?(友情提示:如果不清楚矩阵乘法,请点这里)

2 0 1
1 1 1
0 1 1				//矩阵2

你又发现了什么呢?

好的,如果还没发现,我们再来将矩阵1三次方一下:

1 2 2
2 1 2
2 0 1				//矩阵3

什么,你还没发现吗???

那么让我来告诉你吧!!!

仔细观察矩阵1,我们可以把aij看成通过一条边,由i到达j的情况总数

矩阵2、矩阵3也是如此;

不信?我们举个栗子:

从点1到点2,且通过一条边的情况不存在,记为0;

从点1到点2,且通过两条边的情况共两种(1->2->1 and 1->3->1),记为2;

从点1到点2,且通过三条边的情况仅有一种(1->2->3->1),记为1;

再回头看看矩阵吧!!!是不是完全满足这个条件呢???

所以我们就可以得出结论啦:

在矩阵Ax中,Axij表示由i到j经过x条边的情况总数

所以这就可以运用快速幂啦!!!

仔细算一下时间复杂度,O(n*logn),稳稳滴!!!

那么,这道题就可以很快打出来啦——吗?

显然是不可以的。

可能你已经发现了,我们所有的推论都建立在边权为1的情况上,可是这道题目呢?

接下来有 N N N行,每行一个长度为 N N N的字符串。第 i i i行第 j j j列为 0表示从节点 i i i到节点 j j j没有边,为 1 1 1 9 9 9表示从节点 i i i到节点 j j j需要耗费的时间。

呀呀呀,这道题目的边权不只是1呀!

!(⊙ o ⊙)!

怎么办呢?

第二部分

虽然我们发现不能直接使用我们的结论,但是最大边权是9!N也不超过10!都不算大!

那我们就可以采用一种叫做拆点的方法:把一个点拆成10个点。

并且,我们发现即使如此拆点,N也不会超过100,妥妥的可以呀!

但怎么拆点呢?

我们先来试一下拆一个边权不超过2的图吧!

批注 2020-10-06 184622.png

可得矩阵

0 2
2 1

将其拆点:

To_Heart—题解——SCOI2009迷路_第2张图片

把1.1看成节点1;

把1.2看成节点2;

把2.1看成节点3;

把2.2看成节点4;

可得到新矩阵 :

0 1 0 0
0 0 1 0
0 0 1 1
1 0 0 0

将其平方:

0 0 1 0
0 0 1 1
1 0 1 1 
0 1 0 0

再验算一下,

原来有点1到点2并用经过2边权的方案总数有一种(1->2,边权为2);

现在来说,点1变为点1,点2变为点3,经过2边权的方案总数依旧是2(1->2->3,边权均为1);

那么则说明我们的拆点是正确的。

那么怎么做呢?

我们再对非零点进行分类,原先就有的1看成蓝色,后面通过自连得到的1看成红色:
To_Heart—题解——SCOI2009迷路_第3张图片

那么下面的代码就可以进行拆点操作:


int Cheak(int i,int j){
	return (i-1)*10+j;
}

void ChaiDian(){
	for(int i=1;i<=N;i++){
    	for(int j=1;j<Maxn;j++){          	//Maxn表示最大边权
    		f[Cheak(i,j)][Cheak(i,j+1)]=1;  //对红点标记
		}
    	for(int j=1;j<=N;j++){
    		if(a[i][j]){					//对本来就存在的点进行标记
    			f[Cheak(i,a[i][j])][Cheak(j,1)]=1;
			}
		}
	}
}

那么既然我们通过拆点操作将所有点之间的边权都变成了1,那么我们就可以用刚才得到的定理啦!!!

代码


#include
using namespace std;

int n,t;
const long long Mod=2009;

struct Matrix {
    int Ma[205][205];
    void clear(){
    	memset(Ma,0,sizeof Ma);
	}
}A;

Matrix YunSuan(Matrix x, Matrix y) { 	//乘法 
    Matrix now;
    now.clear();
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
                now.Ma[i][j]=(now.Ma[i][j]+x.Ma[i][k] * y.Ma[k][j])%Mod;
            }
        }
    }
    return now;
}

Matrix Power(Matrix a,int b){		//快速幂 
	Matrix now;
	now.clear();
	for(int i=1;i<=n;i++){
		now.Ma[i][i]=1;
	}
	while(b){
		if(b&1)
			now=YunSuan(now,a);
		a=YunSuan(a,a);
		b>>=1;
	}
	return now;
}

int Cheak(int i,int j){
	return (i-1)*10+j;
}

int main(){
    scanf("%d%d",&n,&t);
    int N=n;
	n*=10;
    for(int i=1;i<=N;i++){
    	for(int j=1;j<10;j++){
    		A.Ma[Cheak(i,j)][Cheak(i,j+1)]=1;
		}
    	for(int j=1;j<=N;j++){
    		int x;
    		scanf("%1d",&x);
    		if(x){
    			A.Ma[Cheak(i,x)][Cheak(j,1)]=1;
			}
		}
	}
	A=Power(A,t);
	printf("%d\n",A.Ma[1][10*N-9]);
}

你可能感兴趣的:(矩阵乘法)