题目传送门点我传送
Ⅰ.字典树+树型DP
非常奇妙的一种解法
第一部分:构建树
先对来的单词读入,插入字典树
然后对于一颗字典树,其实是有很多无用边的,所以我们需要删去一些边
删去非单词节点和非单词节点之间的边,其实就是下面这个函数
void rebuild(int now,int fa) { if(isok[now])//当前节点是单词 { vec[fa].push_back(now);//连边 fa=now;//换爸爸了 } for(int i=1;i<=26;i++) { if(!tree[now][i]) continue; rebuild(tree[now][i],fa);//递归 } }
第二部分:树型DP
对于每一棵子树而言,右选和不选两种方案
选,则子树上的节点都不能再选,即为
\(dp[i][1]=1\)
不选,则子树上的节点可选可不选
\(f[i][0] = \prod_{j\in son[i]}(f[j][0]+f[j][1])\)
因为是计算方案,决策之间是彼此联系的,所以是相乘
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=5009; int n; int tree[maxn][27];int isok[maxn],tot; void insert(string s) { int now=0; for(int i=0;i<s.length();i++) { int k=s[i]-'a'+1; if(!tree[now][k]) tree[now][k]=++tot;//没有节点,新创建一个节点 now=tree[now][k];//去下一个节点 } isok[now]=1;//标记单词 } vector<int>vec[maxn]; void rebuild(int now,int fa) { if(isok[now])//当前节点是单词 { vec[fa].push_back(now);//连边 fa=now;//换爸爸了 } for(int i=1;i<=26;i++) { if(!tree[now][i]) continue; rebuild(tree[now][i],fa);//递归 } } ll dp[maxn][3]; void ddp(int now)//开始DP { dp[now][1]=dp[now][0]=1; for(int i=0;i<vec[now].size();i++)//遍历所有儿子 { int v=vec[now][i]; ddp(v); dp[now][0]=dp[now][0]*(dp[v][0]+dp[v][1]);//不选父节点 } } int main() { cin>>n; string s; for(int i=1;i<=n;i++) cin>>s,insert(s); for(int i=1;i<=26;i++) { if(!tree[0][i]) continue; rebuild(tree[0][i],0);//有结点才向下建树 } ddp(0);//树型DP cout<<dp[0][0]; }
Ⅱ.线性DP
我们预处理一个\(f[i][j]\)表示第i个单词与第j个单词是否能共存,
然后考虑一个\(dp[i]\)表示在前i个单词中,必须包含第i个单词的子集个数,首先\(dp[i]=1\)(只包含自己一个元素的一个子集方案数)
那么如果前面存在一个\(j<i\),并且\(f[i][j]=1\)(即i与j可以共存),那么j所有的子集方案数加上一个i元素就形成了对应新的方案,那么状态就由j转移到i了,最后,直接累计\(dp[1....n]\)即可,当然最后还要算上空集哟。
但是,这么做的前提是单词必须先排序
为什么呢??因为在\(dp[j]\)的方案中,可能有是\(a[i]\)前缀的单词,那我们就不能转移
但如果按照字典序排过之后,就不存在这种问题。
#include <bits/stdc++.h> using namespace std;//dp[i]为必须包括i的个数 typedef long long ll; ll vis[59][59],dp[59]; string a[59]; bool pan(int l,int r) { int p1=0,p2=0; while(p1<a[l].length()&&p2<a[r].length()) if(a[l][p1++]!=a[r][p2++]) return false; return true; } int main() { int n; cin>>n; for(int i=1;i<=n;i++) cin>>a[i],dp[i]=1; sort(a+1,a+1+n); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) if(pan(i,j)) vis[i][j]=1; } for(int i=2;i<=n;i++) { for(int j=1;j<i;j++) { if(vis[i][j]==0)//可以放一起 dp[i]+=dp[j]; } } ll ans=0; for(int i=1;i<=n;i++) ans+=dp[i]; cout<<ans+1; }
来源:https://www.cnblogs.com/iss-ue/p/12566081.html