UOJ Logo zgjkt的博客

博客

【bzoj1875】HH去散步

2015-12-18 13:31:48 By zgjkt

题目大意

给定一个有$n$个点,$m$条边的无向图,每条边的长度都是$1$,没有自环,可能有重边

给定起点$A$与终点$B$,求从起点走$t$条边到达终点的边集个数

注意,每一条选取的边不能是刚刚加入边集的边

数据范围

$n\leqslant 20,m\leqslant 60,t\leqslant 2^{30},0\leqslant A,B$


题解

一开始看到数据范围中的$t\leqslant 2^{30}$,就可以猜想用快速幂矩阵乘法来优化问题了

这道题要求不能走上一条来的边,则可以把无向边分成两条有向边,通过判断有向边之间是否首尾相连建立矩阵$M$

矩阵$M$中$a[i][j]$表示第$i$条有向边走一步可以到第$j$条有向边的方案数(可能有重边)`

然后把矩阵$M$自乘$t-1$次就可以使得,$a[i][j]$表示第$i$条有向边走$t$步可以到第$j$条有向边的方案数

最后创建记录一个从起点$A$,走一步到达任意一条有向边的矩阵$N$,与矩阵$M$相乘得到矩阵$L(L=N\times M)$

直接统计起点$A$到与链接终点$B$的边的方案数之和就是答案了


代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define goedge(i,x) for(int i=last[x];i;i=e[i].next)
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {x=x*10+ch-48;ch=getchar();}
    return x*f;
}


int n,m,t,A,B,sum,et=1;
struct edge{int from,to,next;}e[200];
int last[50],a[200][200],b[200][200],ans[200][200];
inline void addedge(int u,int v){
    e[++et]=(edge){u,v,last[u]};last[u]=et;
    e[++et]=(edge){v,u,last[v]};last[v]=et;
}
inline void mul(int a[200][200],int b[200][200],int result[200][200]){
    int c[200][200];memset(c,0,sizeof(c));
    For(i,1,et) For(j,1,et)
        For(p,1,et) c[i][j]=(c[i][j]+a[i][p]*b[p][j])%45989;
    For(i,1,et) For(j,1,et) result[i][j]=c[i][j];
}

int main(){
    n=read(),m=read(),t=read()-1,A=read(),B=read();
    For(i,1,m) addedge(read(),read());
    For(i,2,et) For(j,2,et)
        if ((e[i].to==e[j].from)&&(i!=(j^1))) a[i][j]++;
    goedge(i,A) b[1][i]++;
    For(i,1,et) ans[i][i]=1;
    while (t){
        if (t&1) mul(ans,a,ans);
        t>>=1;mul(a,a,a);
    }
    mul(b,ans,b);
    goedge(i,B) sum=(sum+b[1][i^1])%45989;
    printf("%d\n",sum);
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。