JZOJ5600. 【NOI2018模拟3.26】Arg

44 篇文章 0 订阅
32 篇文章 0 订阅

题目描述

Description
给出一个长度为 m 的序列 A, 请你求出有多少种 1…n 的排列, 满足 A 是它的一个 LIS.

Input
第一行两个整数 n,m.
接下来一行 m 个整数, 表示 A.
Output
一行一个整数表示答案.

Sample Input
5 3
1 3 4
Sample Output
11

Data Constraint
对于前 30% 的数据, n ≤ 9;
对于前 60% 的数据, n ≤ 12;
对于 100% 的数据, 1 ≤ m ≤ n ≤ 15.

30%

暴力 O(n!) O ( n ! ) 枚举,判断长度是否相等且是否包含该串。
考试是特别sb的写了30分

60%

暴力+优化,具体懒得写不明

70(?)%

暴力+奇怪优化,具体不明

100%

考虑状压。

首先要会 O(nlogn) O ( n log ⁡ n ) 的LIS(具体百度)
接着可以维护它的辅助数组,可以通过辅助数组来构造出原序列

设F[s]表示第i个数的状态
0表示还未放入
1表示放入且在辅助数组里
2表示放入但未在辅助数组里

不难发现,LIS中每次替换掉第一个大于该数的数
在状压中每次寻找一个0,将其改为1,并将其之后第一个1改为2
(其实就是寻找第一个大于该数的数,并将其替换)


这样做还有一个很蛋疼的问题
要保证给定的序列一定是其中一个LIS

首先长度不能超,当前LIS的长度就是辅助数组中元素个数,就是s中1的个数
其次要保证给出序列一定是其一个子序列,那么保证i-1一定在i前插入就行了
(每次加数之前判断前一位是否加进去)

最后答案即为 Σf[s] Σ f [ s ] (s中数全为1或2)

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;

int p[16]={1,3,9,27,81,243,729,2187,6561,19683,59049,177147,531441,1594323,4782969,14348907};
int a[16];
int b[16];
int f[14348907];
int n,m,i,j,k,l,len,sum,I,ans;

int main()
{
    freopen("arg.in","r",stdin);
    freopen("arg.out","w",stdout);

    scanf("%d%d",&n,&m);
    len=p[n]-1;

    scanf("%d",&j);
    fo(i,2,m)
    scanf("%d",&k),a[k]=j,j=k;

    f[0]=1;
    fo(i,0,len-1)
    if (f[i])
    {
        j=i,k=1,sum=0;
        while (j)
        {
            b[k]=j%3;
            j/=3;

            if (b[k++]==1)
            sum++;
        }

        k=0;
        fo(j,1,n)
        if (b[j]==1)
        {
            fo(l,k+1,j-1)
            if (!b[l])//为0
            {
                if (!a[l] || (b[a[l]]>0))//限制顺序
                {
s
                    I=i+p[l-1]+p[j-1];
                    f[I]+=f[i];
                }
            }

            k=j;
        }

        if (sum<m)//限制长度
        {
            fo(l,k+1,n)
            if (!b[l])
            {
                if (!a[l] || (b[a[l]]>0))
                {
                    I=i+p[l-1];
                    f[I]+=f[i];
                }
            }
        }
    }

    ans=0;
    fo(i,1,len)
    if (f[i])
    {
        j=i,k=0;
        while (j)
        {
            if (j%3==0)
            break;

            j/=3;
            k++;
        }

        if (k==n)
        ans+=f[i];
    }

    printf("%d\n",ans);

    fclose(stdin);
    fclose(stdout);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值