HDU 4996 Revenge of LIS(DP)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4996

题意:求1到n的全排列中,有多少个排列的最长上升子列长度为K?

思路:对于当前的最长上升子列,我们记录最后一个值得最小值即可。因此我们用2^n的状态表示当前最长上升子列中使用了哪些数字,且字典序最小。前n-1个数字之后,我们枚举最后一个位置的数字为[1,n]中每个数字,设为k,那么我们只要将前面[1,n-1]组成的数列中所有大于等于k的数字加一即可。

int n,k;



i64 f[22][22];

i64 dp[1<<22],tmp[1<<22];



int cal(int x)

{

    int ans=0;

    int i;

    for(i=0;i<20;i++) if(x&(1<<i)) ans++;

    return ans;

}



void init()

{

    dp[1]=1;

    f[1][1]=1;

    int i,j;

    for(i=1;i<18;i++)

    {



        for(j=0;j<(1<<i);j++) tmp[j]=dp[j];

        for(j=0;j<(1<<(i+1));j++) dp[j]=0;

        for(j=0;j<(1<<i);j++) if(tmp[j])

        {

            int k;

            for(k=0;k<=i;k++)

            {

                int tot=0;

                int c[20];

                int t;

                for(t=0;t<i;t++) if(j&(1<<t)) c[tot++]=t;

                for(t=0;t<tot;t++) if(c[t]>=k) c[t]++;

                c[tot++]=k;



                for(t=0;t<tot;t++) if(c[t]>k)

                {

                    c[t]=k;

                    break;

                }



                int st=0;

                for(t=0;t<tot;t++) st|=1<<c[t];

                dp[st]+=tmp[j];

            }

        }

        for(j=0;j<(1<<(i+1));j++) f[i+1][cal(j)]+=dp[j];

    }

}





int main()

{

    init();

    int T;

    scanf("%d",&T);

    while(T--)

    {

        scanf("%d%d",&n,&k);

        printf("%I64d\n",f[n][k]);

    }

}

 

你可能感兴趣的:(HDU)