如何在算法上优化此代码?为N名学生分配N科目的方法数量?

时间:2015-07-16 08:53:04

标签: c++ algorithm optimization dynamic-programming

  

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

     

每个测试用例以学生数n(1 <= n <= 20)开始。接下来的n行中的每一行包含描述一个学生的偏好的n个整数。 1在第i个位置意味着这个学生喜欢第i个主题,0意味着他绝对不想接受它。

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

这里的面具是主题的一个子集,它显示我拍摄了多少和哪些主题。

重复发生

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

3 个答案:

答案 0 :(得分:2)

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

  • 在中间见面

  • 呼吸第一次搜索

所以,我们注意到,对于第一个人,我们不需要设置所有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次运算。

答案 1 :(得分:1)

你可以通过在内循环上进行一些黑客微观优化来提高速度(在最坏的情况下输入我的计算机上的因子为3.7)。

这个想法是,给定一个二进制数,例如10100,你可以通过操作10100&amp; amp;提取单个设置位。 -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;
}

答案 2 :(得分:0)

嗯, 我发现我们可以让解决方案更快!这是如何!! 我们不需要照顾i循环!为什么?? 如果一个掩码包含i个比特,那么只有它可以获取我们某些方法来做其他明智的它是零,因为一个主题将被分配给每个! 所以现在,重复发生变为

for(每个位掩码) 获取掩码的位数!(这是我唯一与掩码相关联的)。由于上述论点,每个面具只与一个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代码(运行时间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优化它以通过测试数据的时间限制运行时间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;
}