P1666前缀单词

一个人想着一个人 提交于 2020-03-25 14:58:49

题目传送门点我传送

Ⅰ.字典树+树型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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!