[SDOI2009]HH去散步 矩阵乘法

~~~题面~~~

题解:

  很久之前做的这道题,今天看差点没看懂QAQ,赶紧来记录一下。

  考虑f[i][j]表示i和j是否右边相连,有为1,否则为0,那么f同时可以表示从每个点出发走一步到其他点的方案数。

  于是用一个和f长得一模一样的矩阵g来表示从每个点出发到其他点的方案数。

  那么考虑g如何转移。

  其实只要用g*f就可以表示一次转移了。

  为什么?

  设当前转移到了第t次,则g[i][j]表示i到j走t-1次的方案数(因为还没有更新)

  那么矩阵乘法做了什么?

  $g[i][j] = \sum_{l =1}^{n}{g[i][l] * f[l][j]}$

  也就是它枚举了点i走了t - 1次到l,然后再从l走一次到j的方案数。

  是否能转移则要看l 到 j是否有边,而f[l][j]的意义刚好就是这样。

  所以这其实就是做了一个DP,并用矩阵加速。

  但是题目有限制,不能反复走一条边,但我们用点来表示状态显然无法对此做出限制,因此考虑直接用边来设状态。

  g[i][j]表示从边i走到边j的方案数,一个边i可以走到j,当且仅当i的终点是j的起点。

  为了满足题目的限制,我们需要将一条无向边拆分为两条有向边。然后满足上述条件则g[i][j] = 1。

  然后当i是j的反向边时,g[i][j] = 0;

  直接快速幂优化,获得最后的答案只需要再枚举一下哪些边可以到达终点即可

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int 
  4 #define AC 200
  5 #define ACway 5000
  6 #define mod 45989
  7 int n,m,t,A,B,have,answer;
  8 int date[ACway],Next[ACway],Head[AC],tot=1;//变tot为1开始以查询一条边的起点
  9 /*ans:一行,若A可直达(边)则为1,
 10 f:tot*tot 对于每列j而言,若行i满足:date[i](终点) == date[j^1](起点),则为1
 11 have=t-1(第一次已经在ans上了)
 12 因为是用边来转移和判断,所以最后要扫描一遍ans:
 13 若此边可达B,则answer+=ans.s[1][i]
 14 由于习惯问题,,,所有点一开始都+1*/
 15 
 16 struct matrix{
 17     int s[AC][AC];
 18 }ans,f,box;
 19 
 20 inline int read()
 21 {
 22     int x=0;char c=getchar();
 23     while(c>'9' || c<'0') c=getchar();
 24     while(c>='0' && c<='9') x=x*10+c-'0',c=getchar();
 25     return x;
 26 }
 27 
 28 inline void add(int f,int w)
 29 {
 30     date[++tot]=w,Next[tot]=Head[f],Head[f]=tot;
 31     date[++tot]=f,Next[tot]=Head[w],Head[w]=tot;//变无向为有向
 32 }
 33 
 34 void count(matrix x,matrix y)
 35 {
 36     for(R i=1;i<=tot;i++)
 37     {
 38         for(R j=1;j<=tot;j++)
 39         {
 40             box.s[i][j]=0;
 41             for(R l=1;l<=tot;l++)
 42                 box.s[i][j]=(box.s[i][j] + x.s[i][l] * y.s[l][j])%mod;
 43         }
 44     }
 45 }
 46 
 47 void qpow()
 48 {
 49     while(have)
 50     {
 51         if(have & 1) 
 52         {
 53             count(ans,f);
 54             ans=box;
 55         }
 56         have>>=1;
 57         count(f,f);
 58         f=box;
 59     }
 60 }
 61 
 62 void pre()
 63 {
 64     int a,b;
 65     n=read(),m=read(),t=read(),A=read()+1,B=read()+1;
 66     for(R i=1;i<=m;i++)
 67     {
 68         a=read()+1,b=read()+1;
 69         add(a,b);
 70         if(a == A)//如果A可以直达的话
 71             ans.s[1][tot^1]=1;
 72         else if(b == A) ans.s[1][tot]=1;
 73     }
 74     for(R j=2;j<=tot;j++)//枚举列
 75         for(R i=2;i<=tot;i++)//枚举行
 76             if(date[i] == date[j^1] && i != (j^1)) //error!!!这里要判断不是反向边才行,因为一个边的起点就等与反向边的终点啊
 77             //{
 78                 f.s[i][j]=1;
 79             //    printf("%d ---> %d\n",i,j);
 80             //}
 81 /*    printf("ans:\n");
 82     for(R i=2;i<=tot;i++)
 83         printf("%d ",ans.s[1][i]);
 84     printf("\nf:\n");
 85     for(R i=2;i<=tot;i++)
 86     {
 87         for(R j=2;j<=tot;j++)
 88             printf("%d ",f.s[i][j]);
 89         printf("\n");    
 90     }*/
 91     have=t-1;
 92 }
 93 
 94 void work()
 95 {
 96     for(R i=2;i<=tot;i++)
 97         if(date[i] == B) answer+=ans.s[1][i];
 98     answer%=mod;
 99     printf("%d\n",answer);
100 }
101 
102 int main()
103 {
104 //    freopen("in.in","r",stdin);
105     pre();
106     qpow();
107     work();
108 //    fclose(stdin);
109     return 0;
110 }

 

posted @ 2018-08-30 17:28  ww3113306  阅读(187)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。