题解 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 的效率,且空间上也不能承受。

code:

#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

欢迎讨论交流,请点个赞哦~

posted @ 2021-06-27 15:28  Maplisky  阅读(82)  评论(0)    收藏  举报