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


    下午遇到了 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
    ",c==k?ans-1:ans);
    }
    View Code
  • 相关阅读:
    题目1101:计算表达式
    九度oj 题目1107:搬水果
    [Hihocoder] 字符串排序
    [hzwer] 模拟T
    [Luogu] 宝藏
    [Luogu] 列队
    [Luogu] 奶酪
    [Luogu] 逛公园
    [Luogu] 时间复杂度
    [Luogu] 小凯的疑惑
  • 原文地址:https://www.cnblogs.com/kkkek/p/11828021.html
Copyright © 2020-2023  润新知