2018.10.05 NOIP模擬 上升序列(狀壓dp)
阿新 • • 發佈:2018-12-13
描述
給出一個長度為 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好題。 首先需要回憶求的方法,我們會維護一個單調遞增的陣列。 可以設計狀態表示選取的數的集合是,然後d陣列中元素的出現情況是。 這樣轉移是很簡單的。 但時空都無法承受。 於是我們考慮優化,不難發現是的子集。 因此我們三進位制狀壓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;
}