zoukankan      html  css  js  c++  java
  • BZOJ-1002&洛谷P2144【FJOI2007】轮状病毒--py+Java+c++写法(生成树计数-矩阵树-基尔霍夫矩阵-高精度)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1002

    洛谷:

    时间限制1.00s
    内存限制125.00MB
     
    BZOJ: Time Limit: 1 Sec  Memory Limit: 162 MB
     

    Description

      轮状病毒有很多变种,所有轮状病毒的变种都是从一个轮状基产生的。一个N轮状基由圆环上N个不同的基原子
    和圆心处一个核原子构成的,2个原子之间的边表示这2个原子之间的信息通道。如下图所示

      N轮状病毒的产生规律是在一个N轮状基中删去若干条边,使得各原子之间有唯一的信息通道,例如共有16个不
    同的3轮状病毒,如下图所示

      现给定n(N<=100),编程计算有多少个不同的n轮状病毒

    Input

      第一行有1个正整数n

    Output

      计算出的不同的n轮状病毒数输出

    Sample Input

    3

    Sample Output

    16

    其实这道题很简单。。。先给出递推式:

     然后我们py一发:

    n=int(input())
    f=[]
    f.append(1)
    f.append(5)
    for i in range(2,n):
        p=f[i-1]*3-f[i-2]+2
        f.append(p)
    print(f[n-1])

    A了。。。

    大整数嘛,Java同样也挺好用的:

    import java.math.BigInteger;
    import java.util.Scanner;
    
    class Main {
        public static void main(String[] args){
            BigInteger a[]=new BigInteger[120];
            Scanner sc=new Scanner(System.in);
            a[1]=new BigInteger("1");
            a[2]=new BigInteger("5");
            int n=sc.nextInt();
            for (int i=3; i<=n; i++){
                a[i]=a[i-1].multiply(new BigInteger("3")).subtract(a[i-2]).add(new BigInteger("2"));
            }
            System.out.println(a[n]);
        }
    }

    想想还是不能这样。。。我们要知道为什么是这样的,1.打表,2.矩阵树

    打表就算了,矩阵树是计算一个图的生成树的个数的,它是由基尔霍夫矩阵的n-1阶行列式的绝对值得到的,其具体计算过程如下:

    D(i,j)代表第i个点的度,其实只有主对角线有值,E(i,j)代表第i个点到第j个点有边,其中i-i不算,也就是说主对角线全为零,我们可以来试一下本题:

    这个矩阵解出来不难,化为上三角矩阵,做出它的行列式,最后会发现有一行全为0,对角线相乘(剔除0)为16。

    那么当这个3扩大到n的时候这个基尔霍夫矩阵长什么样子呢?可以想到的是,主对角线除了第一行第一列为n,其他的全是3,第一行和第一列的其他元素全为-1,其他主对角线3的两侧有-1,当然,第二列的最后一行和最后一列的第二行也有-1,毕竟首尾跨区域了嘛:

     那么我们现在有两种方法解决这题,一是手动求解,算出递推式,相信高等代数或线性代数学得好的不难求出,然后就是简单的要么py,要么Java来一发,C/C++也可以,只不过比较麻烦,需要大整数。

    手动求解:

    由于是n-1阶子式,所以我们把第一行第一列扔掉是比较容易算的,那么就剩下了主对角线为3,每个3的上下都有一个-1,第一列最下面有个-1,最后一列最上面有个-1。

    然后我们展开就会得到3个子矩阵。。。。balbalbal

    第二种方法就是让计算机求解:

    利用高斯消元,O(N3)的复杂度,n只有100,可以忍受。

    听起来高斯消元有点高大上,其实就是个矩阵化为上三角的过程,模拟一下就OK了,每一行减去初始行,慢慢搞:

    高斯消元打表过程:

    #include <bits/stdc++.h>
    using namespace std;
    
    #define clc(x) memset(x,0,sizeof x);
    
    const int mac=110;
    
    int d[mac][mac],eg[mac][mac];
    double huf[mac][mac];
    
    inline void debug_huf(int n)
    {
        for (int i=1; i<=n; i++)
            for (int j=1; j<=n; j++)
                printf("%.3f%c",huf[i][j],j==n?'
    ':' ');
    }
    
    int main()
    {
        freopen("in.txt","r",stdin);
        int n;
        while (~scanf("%d",&n)){
            if (n==1 || n==2) {
                if (n==2) printf ("5
    ");
                else printf ("%d
    ",1);
                continue;
            }
            n++;
            clc(eg);clc(huf);clc(d);
            for (int i=1; i<=n; i++)
                if (i==1) d[i][i]=n-1;
                else d[i][i]=3;
            for (int i=2; i<=n; i++) eg[i][1]=1,eg[1][i]=1;
            for (int i=2; i<=n; i++) 
                eg[i][i-1]=1,eg[i][i+1]=1,eg[i-1][i]=1,eg[i+1][i];
            eg[2][n]=1;eg[n][2]=1;
            for (int i=2; i<=n; i++)
                for (int j=2; j<=n; j++)
                    huf[i-1][j-1]=d[i][j]-eg[i][j];
            n--;
            for (int i=1; i<n; i++){//第j行-第i行
                for (int j=i+1; j<=n; j++){//第j行的第k个元素改变
                    double p=huf[j][i]/huf[i][i];
                    huf[j][i]=0;
                    for (int k=i+1; k<=n; k++){
                        huf[j][k]-=huf[i][k]*p;
                    }
                }
            }
            //debug_huf(n);
            double ans=1;
            for (int i=1; i<=n; i++) ans*=fabs(huf[i][i]);
            printf("%.0f
    ",ans);
        }
        return 0;
    }
    View Code

    将前10项打出来就是:

    1
    5
    16
    45
    121
    320
    841
    2205
    5776
    15125

     然后利用多元线性关系求出递推关系,就是for几遍,看看能不能找到一个递推关系符合前10项,最后得到3 -1 2。。。。

    我们可以用这个double的范围交一发在洛谷上,看看能过几个测试点,:

    然后想想,我们还有个long double的骚操作,我们改用long double交一发。。一样的结果,看来这些没过的数据确实比较大

    这也是打表。。。是在我们不想写大整数的情况下找到递推式,然后py一发,或者Java一发,如果用py或者Java写高斯消元的话可能会T,特别是Java,递推写法100ms左右,也就是我们只循环了100遍就100ms了,所以Java大概率会T掉。

    当然这里也给出py,Java,C++的本题大整数高斯消元过程:

     py写法:

    n=int(input())
    d=[]
    eg=[]
    huf=[]
    for i in range(n+10):
        d.append([])
        eg.append([])
        huf.append([])
        for j in range(n+10):
            d[i].append(0)
            eg[i].append(0)
            huf[i].append(0)
    if n==1:
        print(1)
    elif n==2:
        print(5)
    else :
        n=n+1
        d[1][1]=n-1
        for i in range(2,n+1):
            d[i][i]=3
        for i in range(2,n+1):
            eg[i][1]=eg[1][i]=1
        for i in range(2,n+1):
            eg[i][i-1]=eg[i+1][i]=eg[i-1][i]=eg[i+1][i]=1
        eg[2][n]=eg[n][2]=1
        for i in range(2,n+1):
            for j in range(2,n+1):
                huf[i-1][j-1]=d[i][j]-eg[i][j]
        n=n-1
        for i in range(1,n):
            for j in range(i+1,n+1):
                p=huf[j][i]/huf[i][i]
                huf[j][i]=0
                for k in range(i+1,n+1):
                    huf[j][k]-=huf[i][k]*p
        ans=1
        for i in range(1,n+1):
            ans*=abs(huf[i][i])
        print('%.0f'% ans)
    View Code

    如何你会发现和之前的double交的测试结果一样。。。和之前py写的递推过程对拍一下发现越到后面会出现越大的误差。。。

    所以真正的高斯消元并不是用浮点数运算的,而是通过更相减损术(辗转相除)实现的!!注意矩阵的行与行之间互换并不影响其行列式的绝对值。

    所以最终AC的py代码:

    n=int(input())
    d=[]
    eg=[]
    huf=[]
    f=[]
    for i in range(n+10):
        d.append([])
        eg.append([])
        huf.append([])
        f.append(0)
        for j in range(n+10):
            d[i].append(0)
            eg[i].append(0)
            huf[i].append(0)
    if n==1:
        print(1)
    elif n==2:
        print(5)
    else :
        n=n+1
        d[1][1]=n-1
        for i in range(2,n+1):
            d[i][i]=3
        for i in range(2,n+1):
            eg[i][1]=eg[1][i]=1
        for i in range(2,n+1):
            eg[i][i-1]=eg[i+1][i]=eg[i-1][i]=eg[i+1][i]=1
        eg[2][n]=eg[n][2]=1
        for i in range(2,n+1):
            for j in range(2,n+1):
                huf[i-1][j-1]=d[i][j]-eg[i][j]
        n=n-1
        ans=1
        for i in range(1,n+1):
            for j in range(i+1,n+1):
                while huf[j][i] :
                    p=huf[i][i]//huf[j][i]
                    for k in range(i,n+1):
                        huf[i][k]-=huf[j][k]*p
                    huf[i],huf[j]=huf[j],huf[i]
                    ans=-ans
            ans*=huf[i][i]
        print(ans)
    View Code

    Java的话就不写D矩阵和E矩阵了,不然会很麻烦,反正最终矩阵我们是知道的,所以直接上手就好了,居然没T掉。。。神奇,看来Java有个基础时间,运行速度倒不是很慢。以下是Java的AC代码:

    import java.math.BigInteger;
    import java.util.Scanner;
    
    class Main {
        public static void main(String[] args){
            BigInteger huf[][]=new BigInteger[120][120];
            Scanner sc=new Scanner(System.in);
            int n=sc.nextInt();
            if (n==1) System.out.println("1");
            else if (n==2) System.out.println("5");
            else {
                for (int i=0; i<=n; i++)
                    for (int j=0; j<=n; j++)
                        huf[i][j]=new BigInteger("0");
                for (int i=1; i<=n; i++){
                    huf[i][i]=new BigInteger("3");
                    huf[i][i-1]=huf[i-1][i]=huf[i][i+1]=huf[i+1][i]=new BigInteger("-1");
                }
                huf[1][n]=huf[n][1]=new BigInteger("-1");
                BigInteger ans=new BigInteger("1");
                for (int i=1; i<=n; i++){
                    for (int j=i+1; j<=n; j++){
                        while (!huf[j][i].equals(new BigInteger("0"))){
                            BigInteger p=huf[i][i].divide(huf[j][i]);
                            for (int k=i; k<=n; k++){
                                huf[i][k]=huf[i][k].subtract(huf[j][k].multiply(p));
                                BigInteger tmp=huf[i][k];
                                huf[i][k]=huf[j][k];
                                huf[j][k]=tmp;
                            }
                            ans=ans.multiply(new BigInteger("-1"));
                        }
                    }
                    ans=ans.multiply(huf[i][i]);
                }
                System.out.println(ans);
            }
        }
    }
    View Code

    接下来就是最难搞的C/C++了。。。不过没办法,以后肯定会遭遇C/C++大整数的,我们这里手写,不用板子(板子太长了,比赛的时候肯定不能用,所以练习一下手敲):

     。。。C++写的。。。矩阵也需要开大整数,然后我觉得直接丢公式吧QAQ,然后就是挺简单的高精度乘低精度,然后高精度直接的减法,当然高精度减法要考虑正负和前导零的,但本题并不会出现这种情况,所以就可以直接愉快的写了:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=200;
    const int mod=10;
    
    vector<int>ans[mac];
    
    vector<int> mult(vector<int> a,int b)
    {
        int len=a.size();
        for (int i=0; i<len; i++)
            a[i]*=b;
        for (int i=0; i<len; i++){
            if (a[i]>=mod){
                int p=a[i]/mod;
                if (i+1==len){
                    a[i]-=p*mod;
                    a.push_back(p);
                }
                else {
                    a[i]-=p*mod;
                    a[i+1]+=p;
                }
            }
        }
        return a;
    }
    
    vector<int> subct(vector<int>a,vector<int>b)
    {
        int lena=a.size(),lenb=b.size();
        for (int i=0; i<min(lena,lenb); i++){
            a[i]-=b[i];
        }
        for (int i=0; i<lena; i++){
            while (a[i]<0) a[i]+=10,a[i+1]-=1;
        }
        return a;
    }
    
    vector<int> add(vector<int>a,int b)
    {
        a[0]+=b;
        int i=0; 
        while (a[i]>=10){
            a[i]-=10;a[i+1]++;
            i++;
        }
        return a;
    }
    
    void out(vector<int>v)
    {
        int len=v.size();
        for (int i=len-1; i>=0; i--)
            printf("%d",v[i]);
        printf("
    ");
    }
    
    int main()
    {
        //freopen("in.txt","r",stdin);
        int n;
        scanf ("%d",&n);
        ans[1].push_back(1);
        ans[2].push_back(5);
        for (int i=3; i<=n; i++){
            vector<int>p=mult(ans[i-1],3);
            ans[i]=subct(p,ans[i-2]);
            ans[i]=add(ans[i],2);
        }
        out(ans[n]);
        return 0;
    }
    路漫漫兮
  • 相关阅读:
    Hadoop学习资料收集
    sed使用详解
    shell编程001
    电影《无法触碰》
    正则表达式
    I/O重定向与管道
    bash基础知识
    用户权限模型
    Linux文件管理常用命令
    根文件系统详解
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/12037948.html
Copyright © 2011-2022 走看看