1. 程式人生 > >2018.10.05 NOIP模擬 上升序列(狀壓dp)

2018.10.05 NOIP模擬 上升序列(狀壓dp)

描述

給出一個長度為 m 的上升序列 A(1 ≤ A[i]≤ n), 請你求出有多少種 1…n 的排列, 滿足 A 是它的一個 LIS.

輸入

第一行兩個整數 n,m. 接下來一行 m 個整數, 表示 A.

輸出

一行一個整數表示答案.

樣例

【輸入樣例1】 5 3 1 3 4 【輸出樣例1】 11 【輸入樣例2】 4 2 3 4 【輸出樣例2】 5

提示

【資料範圍與約定】 對於前 30% 的資料, n ≤ 9; 對於前 60% 的資料, n ≤ 12; 對於 100% 的資料, 1 ≤ m ≤ n ≤ 15.

狀壓dp好題。 首先需要回憶O(nlogn)O(nlog n)lis

lis的方法,我們會維護一個單調遞增的dd陣列。 可以設計狀態f(s1,s2)f(s1,s2)表示選取的數的集合是s1s1,然後d陣列中元素的出現情況是s2s2。 這樣轉移是很簡單的。 但時空都無法承受。 於是我們考慮優化,不難發現s1s1s2s2的子集。 因此我們三進位制狀壓dp就行了。 程式碼:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int sta1[20],sta2[20],n,m,a[20];
ll f[14348908],ans=0;
inline void
dfs(int pos,int dep,int sta){ if(pos==n+1){if(!dep)ans+=f[sta];return;} sta+=sta2[pos-1],dfs(pos+1,dep,sta),sta+=sta2[pos-1],dfs(pos+1,dep-1,sta); } inline bool check(int x){ bool flag=1; for(int i=1;i<=m;++i) if((x&sta1[a[i]-1])==0)flag=0; else if(!flag)return false; return true; } int
main(){ scanf("%d%d",&n,&m),sta1[0]=sta2[0]=1; for(int i=1;i<=n;++i)sta1[i]=sta1[i-1]*2,sta2[i]=sta2[i-1]*3; for(int i=1;i<=m;++i)scanf("%d",&a[i]); f[0]=1; for(int i=0;i<sta1[n];++i){ if(!check(i))continue; int sub=i; for(int sub=i;1;sub=(sub-1)&i){ int sta=0; for(int j=1;j<=n;++j){ if(sub&sta1[j-1])sta+=sta2[j-1]; if(i&sta1[j-1])sta+=sta2[j-1]; } if(f[sta]) for(int j=1;j<=n;++j){ if(sta/sta2[j-1]%3==0){ int msta=sta+2*sta2[j-1]; for(int k=j+1;k<=n;++k)if(msta/sta2[k-1]%3==2){msta-=sta2[k-1];break;} f[msta]+=f[sta]; } } if(!sub)break; } } dfs(1,m,0),cout<<ans; return 0; }