zoukankan      html  css  js  c++  java
  • P1666前缀单词

    题目传送门点我传送

    Ⅰ.字典树+树型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_{jin 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;
    }
    
  • 相关阅读:
    《网络攻防第四周作业》
    《网络攻防第三周作业》20179313
    15.javaweb XML详解教程
    小程序新功能:直接进入内嵌网页!
    为什么要创业?听听扎克伯格怎么说
    面试官:“还有什么问题问我吗?”我...
    双十一为何规则复杂,套路多多
    如何设置电信光猫?图解手把手教你(超级详细)
    14.javaweb AJAX技术详解
    android黑科技系列——自动注入代码工具icodetools
  • 原文地址:https://www.cnblogs.com/iss-ue/p/12566081.html
Copyright © 2011-2022 走看看