1. 程式人生 > 其它 >題解 P6873 [COCI2013-2014#6] FONT

題解 P6873 [COCI2013-2014#6] FONT

題目傳送門

實話說,別想太多,就能速切本題。

演算法分析:記憶化搜尋

第一眼看到可能會想到狀壓 dp,但事實上沒那麼複雜。如果要狀壓的話,狀態數有 \(N\times 2^{26}=6710886400\) 種,顯然在時空上均無法接受。

考慮記憶化搜尋。簡單地,用 map 記錄狀態。由於順序無關,每一輪搜尋可以從上一輪之後開始。為了記憶化簡單,我們可以把已經包含字元的狀態壓縮在 \(sta\) 裡,然後用 \(f_{fro,sta}\) 表示到 \(fro\) 時,使用狀態為 \(sta\) 的情況總數。然後搜尋即可。

下面說明記憶化搜尋比狀壓 dp 優秀。

狀壓 dp 雖然常數較小,但所有狀態幾乎是跑滿的。由於僅有 \(26\)

個字元,且一個單詞很可能包含多種字元,因此在大多數情況下,很快就能組合成一個測試句且情況很容易重複。在已經成為測試句的情況下,剩餘的單詞選與不選均不會產生影響。記這時已經使用到第 \(fro\) 個單詞,那麼可以直接返回 \(2^{n-fro}\) 作為答案。

在最壞情況下,每一層只能獲得一個新字元。由於單詞長度較長(不超過 \(100\)),因此這種情況很難出現。如果出現,可以加入特判剔除這種狀況(然鵝本題中並沒有這樣噁心的資料)。

需要注意的是,在 map 中進行查詢時,最好不要直接將狀態作為下標進行查詢。因為在 map 的機制裡,若找不到目標,將自動建立一個空值。而 map 的效率與其元素個數直接相關。這樣做將大幅降低 map 的效率,且空間上也不能承受。

下面給出程式碼:

#include<bits/stdc++.h>
#define reg register
#define ll long long
#define F(i,a,b) for(reg int i=a;i<=b;++i)
using namespace std;
inline int read();
const int N=105,M=30,all=(1<<26)-1;
int b[N],n;
char c[N];
ll ans,pww[M] {1};
map<int,ll>f[M];
ll dfs(int fro,int sta){
    if(sta==all)return pww[n-fro];
	//直接計算得出
    if(f[fro].find(sta)!=f[fro].end())return f[fro][sta];
    //用 map.find 不會建立空值 
    ll ans=0;
    F(i,fro+1,n)ans+=dfs(i,sta|b[i]);
    f[fro][sta]=ans;//記憶化 
    return ans;
}
int main() {
    F(i,1,26)pww[i]=pww[i-1]<<1ll;
    n=read();
    F(i,1,n) {
        scanf("%s",c+1);
        int len=strlen(c+1);
        F(j,1,len)b[i]|=(1<<(c[j]-'a'));//狀態壓縮 
    }
    ans=dfs(0,0);
    printf("%lld",ans);
    return 0;
}
inline int read() {
    reg int x=0;
    reg char c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x;
}

AC

歡迎討論交流,請點個贊哦~