bzoj 3591

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

Solution

因为n只有15左右,可以考虑状压DP
设f[s1][s2]表示选取s1的状态,而g中状态为s2(g[i]为长度为i的最小的结尾大小,g是递增的,在为1,不在为0)的方案数
显然可以整合成一个三进制状态s(0表示不选,1表示选但不在g中,2表示选且在g中(在g中的一定是被选过的))
对于当前状态s,它可以选取某个为0的位置(但是要保证a中的数是依次出现),然后更新状态。
因为可能有多种状态可以推至同一状态,所以按0的个数bfs处理,最后统计0的个数为0的状态且LIS为m的f[x]的和(可以计算x中2的个数)
时间复杂度为O(n*3^n),稍微需要卡常。

#include
#include
#include
#include
#include
#include
using namespace std;

#define M 15000000
#define N 20

int f[M],a[N],b[N],p[N],c[N],cnt[M];
int n,m,ans=0;
vector<int> t[N];

int main()
{
    freopen("arg.in","r",stdin);
    freopen("arg.out","w",stdout);
    scanf("%d%d",&n,&m);
    memset(b,0,sizeof(0));
    p[1]=1;
    for (int i=2;i<=n+1;i++) p[i]=p[i-1]*3;
    for (int i=1;i<=m;i++) 
    {
        scanf("%d",&a[i]);
        b[a[i]]=i;
    }
    memset(cnt,0,sizeof(cnt));
    for (int i=0;i1];i++)
    {
        cnt[i]=cnt[i/3]+(i%3!=0);
        t[n-cnt[i]].push_back(i);
    }
    memset(f,0,sizeof(f));
    f[0]=1; c[n+1]=n+1;
    int last,x,y,z,s;
    for (int ii=n;ii>=0;ii--)
    {
        for (int j=1;j<=t[ii].size();j++)
        {
            if (f[t[ii][j]]<=0) continue;
            last=0,x=t[ii][j],y,z,s=0;
            for (int i=1;i<=m;i++)
              if ((x/p[a[i]])%3==0) {y=i; break;}
        for (int i=n;i>=1;i--)
            if ((x/p[i])%3==2) c[i]=i;
            else c[i]=c[i+1];
            for (int i=1;i<=n;i++)
            {
              if (y>=b[i] && (x/p[i])%3==0)
                {
                    z=x+2*p[i];
                    z-=p[c[i]]*(c[i]!=n+1);
                    f[z]+=f[x];
                }
                last+=((x/p[i])%3==2);
            }
            ans+=f[x]*(ii==0)*(last==m);
        }
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(状压DP)