码迷,mamicode.com
首页 > 其他好文 > 详细

关于邻接矩阵的拆点 和一些杂七杂八想不到的做法

时间:2019-11-09 23:17:40      阅读:94      评论:0      收藏:0      [点我收藏+]

标签:堆栈   void   遇到   倍增   初始   strong   solution   target   cli   

 

下午遇到了 LuoguP3597LuoguP4159

这应该是我在网络流后第二次遇到的拆点。这两道题是结合邻接矩阵和拆点。

 

邻接矩阵有一个性质:设邻接矩阵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\n",c==k?ans-1:ans);
}
View Code

 

 

关于邻接矩阵的拆点 和一些杂七杂八想不到的做法

标签:堆栈   void   遇到   倍增   初始   strong   solution   target   cli   

原文地址:https://www.cnblogs.com/kkkek/p/11828021.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!