【问题标题】:How to optimise this code Algorithmically? Number of ways to assign N subjects to N students?如何在算法上优化此代码?将 N 个科目分配给 N 个学生的方法有多少?
【发布时间】:2015-07-16 08:53:03
【问题描述】:

您的任务是计算 n 的不同分配的数量 给 n 个学生提供不同的主题,这样每个人都能得到一个 他喜欢的话题。

每个测试用例以学生人数 n (1

我通过定义 DP[i][mask] 来表示仅使用 i 元素形成掩码集的方法的数量来解决这个问题!

这里的 mask 是 Subjects 的一个子集,它显示了有多少科目和哪些科目。

重复出现

for(i=1;i<N;i++)    //Student
    for(j=1;j<(1<<N);j++)   //Subject Subset
    {
        for(k=0;k<N;k++)        //Selecting subject
            if( (j&(1<<k)) && A[i][k] )
                DP[i][j]+=DP[i-1][j^(1<<k)];
    }

即从第 i 个学生最喜欢的科目中取一门科目并递归到较低的州!

但是,这还不够,因为解决方案的复杂度为 O(2^N * N^2)。

我们至少需要降低一个 N!

如何降低这个问题的复杂性?这是我的代码:

#include<bits/stdc++.h>
using namespace std;
long long DP[20][(1<<20)+1];
int main()
{
    int T;
    scanf("%d",&T);
    for(;T;--T)
    {
        int N,i,j,k;

        scanf("%d",&N);

        int A[N+1][N+1];

        for(i=0;i<N;i++)
            for(j=0;j<N;j++)
                scanf("%d",&A[i][j]);
        /*

        First of all let's think about the state!!
        DP[i][j] where i is the i th student I am considering j is a bitmask which tells me which all subjects are
        Done!!
        ********All Right************
        So what can the recurrence be..?
        traverse over the array A[i][]
        If We can use the k th element of i.e A[i][k].
        We need to try assigning it and Get the number of ways
        *********Seems Fine *********
        What will be the base case??
        When only one element left in the mask and i is 1 we won't traverse more down!!

        **OK**
        SO what is the topological order of DP states !>>>????

        I dont Know!! Let's think... Let me explain ummmmmmmmmmmmmmmmmmmmmmmmmmmm
        ummmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
        I am like calling a smaller i with smaller subset!
        for every i
            go in the order of increasing subsets

        I think that should work!! Let's see
        */
        for(i=0;i<(1<<N);i++)
            DP[0][i]=0;
        for(i=0;i<N;i++)
                    if(A[0][i])
                        DP[0][1<<i]=1;

        for(i=1;i<N;i++)    //Student
            for(j=1;j<(1<<N);j++)   //Subject Subset
            {
                DP[i][j]=0;
                for(k=0;k<N;k++)        //Selecting subject
                    if( (j&(1<<k)) && A[i][k] )
                        DP[i][j]+=DP[i-1][j^(1<<k)];
            }
        long long ans=0;

        for(i=1;i<(1<<N);i++)
            ans+=DP[N-1][i];
        printf("%lld\n",ans);
    }
    return 0;
}

问题链接以备不时之需:Spoj

【问题讨论】:

    标签: c++ algorithm optimization dynamic-programming


    【解决方案1】:

    您可以使用两种技术来降低时间复杂度:

    • 在中间相遇

    • 呼吸优先搜索

    所以,我们注意到,对于第 i 个人,我们不需要设置所有 n 位,而只需要设置 i 位,因此不是遍历 (0 到 2^n) 的所有数字,我们只需要遍历所有设置了 i 位的数字。我们可以通过使用 BFS 来做到这一点

    其次,如果我们使用一个数组dp来存储分配主题给前半部分n/2人的方式数,另一个数组dp1存储分配主题到后半部分的方式数量n/ 2 人,因此将主题分配给所有 n 人的方法数为

    int x = a number that has n/2 bit set
    
    int result = sum (dp[x] + dp1[2^n - 1 - x]);
    

    时间复杂度将为 C(n, n/2)*n/2*n,其中 n = 20 ~ 3*10^7 次操作。

    【讨论】:

    • 我认为我们也可以这样改进子集已经包含了我们正在查看的学生人数的信息!
    【解决方案2】:

    您可以通过内部循环的一些黑客微优化来提高速度(在最坏的情况下输入我的计算机上提高 3.7 倍)。

    这个想法是,给定一个二进制数,例如 10100,您可以通过操作 10100 & -10100 = 00100 提取单个设置位。

    因此,我们可以使用以下代码将 k 上的循环更改为仅循环重要位:

    #include<bits/stdc++.h>
    using namespace std;
    long long DP[20][(1<<20)+1];
    int main()
    {
        int T;
        scanf("%d",&T);
        for(;T;--T)
        {
            int N,i,j,k;
            int masks[20];           // ADDED
    
            scanf("%d",&N);
    
            int A[N+1][N+1];
    
            for(i=0;i<N;i++) {
                masks[i] = 0;
                for(j=0;j<N;j++) {
                    scanf("%d",&A[i][j]);
                    masks[i] |= A[i][j]<<j; // ADDED
                }
            }
    
            for(i=0;i<(1<<N);i++)
                DP[0][i]=0;
            for(i=0;i<N;i++)
                        if(A[0][i])
                            DP[0][1<<i]=1;
    
            for(i=1;i<N;i++)    //Student
                for(j=1;j<(1<<N);j++)   //Subject Subset
                {
                    long long t = 0;           // ADDED
                    int mask = j & masks[i];   // ADDED
                    while(mask) {              // ADDED
                      int bit = mask & -mask;  // ADDED
                      t += DP[i-1][j - bit];   // ADDED
                      mask -= bit;             // ADDED
                    }                          // ADDED
                    DP[i][j]=t;                // ADDED
                }
            long long ans=0;
    
            for(i=1;i<(1<<N);i++)
                ans+=DP[N-1][i];
            printf("%lld\n",ans);
        }
        return 0;
    }
    

    【讨论】:

    • 如何学习编写优化代码?您如何知道某些运算符的性能优于其他运算符?而且确实解决了问题!!在我看来,这只是改变做事方式而不是算法!!
    • 一般来说条件代码(if 语句)是不好的,因为它们会导致指令流水线需要被刷新,并尽量减少内存访问(或至少使它们适合数据缓存)
    【解决方案3】:

    嗯, 我发现我们可以更快地解决问题!这是怎么回事!! 我们不需要照顾 i 循环!为什么?? 如果掩码包含 i 位,那么只有它可以为我们提供一些方法,否则它为零,因为要为每个主题分配一个主题! 所以现在,循环变为

    for(每个位掩码) 获取掩码的位数!(这是唯一与掩码关联的 i)。由于上述论点,每个掩码仅与一个 i 相关联!

    通过向学生提供所有有利的科目来检查我们可以达到当前状态的所有方式!现在它将导致一个低位数的掩码,该掩码也与唯一的 i 相关联,特别是 i-1 !

    所以现在,我们可以有一个一维 DP 来应对这种情况真是太好了……

    for(j=1;j<(1<<N);j++)   //Subject Subset
         {
                DP[j]=0;
                i=__builtin_popcount(j);
                for(k=1;k<=N;k++)        //Selecting subject
                    if( (j&(1<<(k-1))) && A[i][k] )
                            DP[j]+=DP[j^(1<<(k-1))];
        }
    

    我的 AC CODE(运行时间 0.78)通过预处理位计数而不是 __builtin_popcount() 进行了改进

    #include<bits/stdc++.h>
    using namespace std;
    long long DP[(1<<20)+1];
    int main()
    {
        int T;
        scanf("%d",&T);
        for(;T;--T)
        {
            int N,i,j,k;
    
            scanf("%d",&N);
    
            int A[N+1][N+1];
    
            for(i=1;i<=N;i++)
                for(j=1;j<=N;j++)
                    scanf("%d",&A[i][j]);
    
    
            for(i=0;i<(1<<N);i++)
                DP[i]=0;
            DP[0]=1;
    
            for(j=1;j<(1<<N);j++)   //Subject Subset
                 {
                        DP[j]=0;
                        i=__builtin_popcount(j);
                        for(k=1;k<=N;k++)        //Selecting subject
                            if( (j&(1<<(k-1))) && A[i][k] )
                                    DP[j]+=DP[j^(1<<(k-1))];
                }
    
            long long ans=0;
            printf("%lld\n",DP[(1<<N)-1]);
        }
        return 0;
    }
    

    与@Peter 优化的代码相比。他能够对其进行优化,以通过 spoj 的测试数据通过时间限制 Run Time 2.60 秒!

    #include<bits/stdc++.h>
    using namespace std;
    long long DP[20][(1<<20)+1];
    int main()
    {
        int T;
        scanf("%d",&T);
        for(;T;--T)
        {
            int N,i,j,k;
            int masks[20];           // ADDED
    
            scanf("%d",&N);
    
            int A[N+1][N+1];
    
            for(i=0;i<N;i++) {
                masks[i] = 0;
                for(j=0;j<N;j++) {
                    scanf("%d",&A[i][j]);
                    masks[i] |= A[i][j]<<j; // ADDED
                }
            }
    
            for(i=0;i<(1<<N);i++)
                DP[0][i]=0;
            for(i=0;i<N;i++)
                        if(A[0][i])
                            DP[0][1<<i]=1;
    
            for(i=1;i<N;i++)    //Student
                for(j=1;j<(1<<N);j++)   //Subject Subset
                {
                    long long t = 0;           // ADDED
                    int mask = j & masks[i];   // ADDED
                    while(mask) {              // ADDED
                      int bit = mask & -mask;  // ADDED
                      t += DP[i-1][j - bit];   // ADDED
                      mask -= bit;             // ADDED
                    }                          // ADDED
                    DP[i][j]=t;                // ADDED
                }
            long long ans=0;
    
            for(i=1;i<(1<<N);i++)
                ans+=DP[N-1][i];
            printf("%lld\n",ans);
        }
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2017-09-08
      • 2016-07-17
      • 2019-12-23
      • 2018-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-14
      相关资源
      最近更新 更多