1. 程式人生 > >【2018/10/05】T2

【2018/10/05】T2

我好菜啊……好菜啊……好菜啊……

上升序列

描述

給出一個長度為 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.

分析

這個資料範圍很狀壓,但我依舊沒有看出來,為什麼那麼弱啊%>_<%(我好菜啊……好菜啊……好菜啊……)

但即使知道是狀壓,也沒搞出來

後面在ldx大佬的耐心講解下,總算是會了,%%%%%%%%廣告%%%%%%%%%

先知道最長不降子序列的一種求法是(就是那個優秀演算法 nlogn):使用附加陣列D[i],表示當前長度為 i 的最長不降子序列最小的結尾是多少,顯然它是單調遞增的。每插入一個數就二分找到第一個大於它的位置替換。 

我們可以設狀態S1,第i位為 1表示它在D 中出現了,出現的位置就是1~i位 1 的數量  現在就可以設遞推狀態 f (S,S1) 表示當前用了S中的數(就是1~n),D的狀態為S1的方案數  轉移很容易轉移,列舉這一位選什麼直接更新  

但如果簡單這樣搞的話,時間和空間限制都會受不了啊,因此我們再觀察一下,注意到S1一定是S的子集,因為要在D中出現,那麼這個數肯定被 S 選了,於是可以用三進製表示  

然後我就懵了,什麼三進製表示????

好吧,其實就是這樣的,

0表示這一位的數既沒有在S中出現,又沒有在S1中出現

1表示在S中出現了或者在S1中出現了

2表示既在S中又在S1中

然後就亂搞了……看程式碼吧,雖然ldx大佬diss我誰還會加註釋啊,但我依然要加,╭(╯^╰)╮

程式碼

#include <bits/stdc++.h>
#define ll long long//注意一下範圍咯
using namespace std;
int n,m,a[20],s1[20],s2[20];
ll f[14348990],ans=0;//f陣列的大小 3^15
bool check(int sta){//a陣列的數必須是有順序有先後關係的出現
	int flag=0;
	for(int i=1;i<=m;++i){
		if((sta&s1[a[i]-1])==0) flag=1;
		else if(flag) return false ;
	}
	return true;
}
void dfs(int pos,int use,int sta){//統計答案,選了n個數,且LIS的長度為m
	if(pos==n+1){if(!use) ans+=f[sta];return;}
	sta+=s2[pos-1];dfs(pos+1,use,sta);
	sta+=s2[pos-1];dfs(pos+1,use-1,sta);
}
int main(){
	int i,j,k;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i) scanf("%d",&a[i]);
	s1[0]=s2[0]=1;
	for(i=1;i<=15;++i) s1[i]=s1[i-1]*2,s2[i]=s2[i-1]*3;
	f[0]=1;//dp問題的初始值
	for(i=0;i<s1[n];++i){//列舉S的所有狀態
		if(!check(i)) continue;
		int sub=i;
		for(int sub=i;1;sub=(sub-1)&i){//列舉S1,因為S1是S的子集嘛
        //這個列舉方法一定要學到
			int sta=0;//現在就根據S和S1推出三進位制的表示
			for(int j=1;j<=n;++j){
				if(i&s1[j-1]) sta+=s2[j-1];
				if(sub&s1[j-1]) sta+=s2[j-1];
			}
			if(f[sta]){//往下轉移 
				for(int j=1;j<=n;++j){
					int msta=sta;
					if(msta/s2[j-1]%3==0){//如果這個數沒有被使用過
						msta+=2*s2[j-1];
						for(int k=j+1;k<=n;++k) if(msta/s2[k-1]%3==2) {msta-=s2[k-1];break;	}//更新d陣列,不清楚的同學可以參見我之前的部落格
						f[msta]+=f[sta];//累計
					} 
				}
			}
			if(!sub) break;
		}
	}
	dfs(1,m,0);
	cout<<ans;
	return 0;
}