关于邻接矩阵的拆点 和一些杂七杂八想不到的做法
Posted kkkek
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于邻接矩阵的拆点 和一些杂七杂八想不到的做法相关的知识,希望对你有一定的参考价值。
下午遇到了 LuoguP3597和LuoguP4159
这应该是我在网络流后第二次遇到的拆点。这两道题是结合邻接矩阵和拆点。
邻接矩阵有一个性质:设邻接矩阵A,则在矩阵Ak中,点aij的值表示从点i到j长度为k的通路数量。长度表示边的个数。
P4159 要求的是:在有向图中,从起点到终点的路径权值和为k的路径数。
题目里,点的个数为n<=10,边的权值为1<=w<=9;
由于边的权值w很小,所以可以拆点,是边的权值为1,分为多条边。这样子就可以用邻接矩阵表示整个一阶有向图的连通关系。
所以从起点到终点 路径权值和为k的路径数,等效于,从起点到终点长度为k的通路数。
所以,拆点后需要求的是,在k阶邻接矩阵中A[st][ed]的值。k很大没关系,矩阵快速幂即可。
至于怎么拆点,P4159的题解写的很清楚。
·设整个有向图的边权值最大值为w,把每个点拆成w个点;
·令有序对(i,j) (i∈[1,n]∩Z,j∈[0,w-1]∩Z)表示点 i 拆成的第 j 个点,其中第 0 个点是“真”点,其余的是“假”点;
·令(i,j) (j∈[1,w-1]∩Z) 向 (i,j-1)连一条边权为 1的边;
·对于原图 若 u到v有一条权值为w‘的边 则令(u,0)到(v,w‘-1)连一条权值为1的边。
对于P3597
要求的是k短路,每个点每条边可以重复经过。
一开始想到的是19年ccpc网络赛的1004,那时要求的也是k短路,但是那时k正常大,用的是单调堆栈,这道题不行。k<=1e18。
然后, 边权:1<=w<=3!
所以还是邻接矩阵。
但不同上面那题,给出长度求出个数,这次是给出个数求长度。有点麻烦。
很麻烦。
因为我不会。
所以对着唯一的一篇题解琢磨了很久。
题解有一个巧妙的做法,无法说出它为什么对,但动手模拟了一下,它就是对的。
先这样:
·设初始矩阵为 f
·令f[0][0]=1;注:题目 对于顶点u 1<=u<=n,即u!=0;
·对于所有1<=i<=n,令f[i][0]=1;
之后,跟普通的拆边一样连点。
这样做的结果是:
对于k阶矩阵f^k 的每一个fk[i][0] 表示的是:以i为起点,长度小于k的 通路的数量+1
即 num=fk[i][0]-1 :以点i为起点 长度小于k的通路数有num条。
之后是倍增记录矩阵的状态,然后通过计算每个矩阵 当前 长度内 的通路数量 来 判断长度所在的区间,然后再去处理细节。
倍增真的是一个很巧妙的做法啊。
#include<bits/stdc++.h> #define debug printf("!"); #define mp make_pair using namespace std; typedef long long ll; const int maxn=5e3+50; const int inf=0x3f3f3f3f; const int N=155; struct P{ ll a[N][N]; }f[70]; void mul(ll a[][N],ll b[][N],int n,ll res[][N]) { for(int i=0;i<n;i++) for(int j=0;j<n;j++) { res[i][j]=0; for(int k=0;k<n;k++)res[i][j]+=a[i][k]*b[k][j]; } } ll cal(ll a[][N],int n,ll k) { ll res=0; for(int i=1;i<=n;i++) { res+=a[i][0]-1; if(k<res)return res; } return res; } void print(ll a[][N],int n) { for(int i=0;i<n;i++) { for(int j=0;j<n;j++)printf("%d ",a[i][j]); putchar(10); } } void copy(ll a[][N],ll b[][N],int n) { for(int i=0;i<n;i++) for(int j=0;j<n;j++)a[i][j]=b[i][j]; } int main() { int n,m,i,u,v,w;ll k; scanf("%d%d%lld",&n,&m,&k); f[0].a[0][0]=1; for(i=1;i<=n;i++) { f[0].a[i][0]=1; f[0].a[i+n][i]=1; f[0].a[i+n+n][i+n]=1; /* 使得所有的点都连向0点 之后统计f^k 的a[i][0] 表示的是 以i点为起点 长度在k之内的边的个数+1 至于为什么,手动 画图,和邻接矩阵 就懂了... */ } while(m--) { scanf("%d%d%d",&u,&v,&w); f[0].a[u][v+(w-1)*n]++; } ll temp[N][N],t[N][N],ans=0,c; for(i=0;i<3*n+1;i++)t[i][i]=1; for(i=1;i<=64;i++) { mul(f[i-1].a,f[i-1].a,3*n+1,f[i].a); if(cal(f[i].a,n,k)>=k)break; } if(i==65) { puts("-1");return 0; } for(;i>=0;i--) { mul(t,f[i].a,3*n+1,temp); c=cal(temp,n,k); if(c<=k) { copy(t,temp,3*n+1); ans+=1ll<<i; } if(c==k)break; } printf("%lld ",c==k?ans-1:ans); }
以上是关于关于邻接矩阵的拆点 和一些杂七杂八想不到的做法的主要内容,如果未能解决你的问题,请参考以下文章